Xiper

Шаблоны проектирования JavaScript

Автор: Валерий Съестов Дата публикации:

Шаблоны проектирования, предлагают решение наиболее типичных задач, связанных с архитектурой объектно-ориентированного программного обеспечения. Они достаточно давно используются на практике и доказали свою полезность во многих ситуациях. Именно поэтому и вам будет полезно познакомиться с ними и обсудить их.

Хотя эти шаблоны проектирования могут применяться в любом языке программирования, тем не менее многие годы они изучаются с позиций языков со строгим контролем типов и со статическими классами, таких как C++ и Java.

JavaScript, будучи динамическим нетипизированным языком, опирающимся на использование прототипов, иногда позволяет удивительно легко и даже тривиально реализовать некоторые их этих шаблонов.

Нами будут рассмотрены следующие шаблоны проектирования:

  • Пространство имен
  • Частные свойства и методы
  • Модуль
  • Шаблон модуль выявление
  • Одиночка
  • Наблюдатель
  • Посредник
  • Прототип
  • Команда
  • Фасад
  • Фабричный метод
  • Смешанный шаблон
  • Декоратор
  • Приспособленец

Часть 3.1 Шаблон “Пространство имен”

Пространства имен помогают уменьшить количество глобальных переменных, необходимых нашим программам, и одновременно избежать конфликтов имен и чрезмерного употребления префиксов.

Взгляните на следующий пример:

// ДО: 5 глобальных переменных
function Parent() {}
function Child() {}
var some_var = 1;
var module1 = {};
module1.data = {a: 1, b: 2};
va module2 = {};

Такой программный код легко переписать иным способом, создав единственный глобальный объект, назовем его MYAPP, и превратив все функции и переменные в свойства этого глобального объекта:

// ПОСЛЕ: 1 глобальная переменная 
// глобальный объект 
var MYAPP = {}; 
// конструкторы 
MYAPP.Parent = function () {}; 
MYAPP.Child = function () {}; 
// переменная 
MYAPP.some_var = 1; 
// обьект-контейнер 
MYAPP.modules = {}; 
// вложенные обьекты 
MYAPP.modules.modulel = {}; 
MYAPP.modules.modulel.data = {a: 1, b: 2}; 
MYAPP.modules.module2 = {}; 

С ростом сложности программы некоторые фрагменты программного кода приходится выносить в отдельные файлы и подключать их при определенных условиях. Вследствие этого становится безосновательным предполагать, что ваш файл первым определит некоторое пространство имен или свойство в нем. Вполне возможно, что некоторые свойства, которые предполагается добавить, уже существуют, и вы можете затереть их по неосторожности. Поэтому, прежде чем добавлять свойство или создавать пространство имен, желательно убедиться, что оно еще не создано.

Поэтому было бы очень удобно иметь функцию, которая взяла бы на себя выполнение всех операций, необходимых для создания пространства имен. Назовем эту функцию namespace() и положим, что она должна использоваться, как показано ниже:

// применение функции пространства имен 
MYAPP.namespace('MYAPP.modules.module2'); 
// этот вызов эквивалентен следующей конструкции: 
// 	var MYAPP = { 
//		modules: { 
// 			module2: {} 
// 		} 
// 	}; 

Далее приводится пример реализации этой функции, в котором использован принцип неразрушения, то есть если пространство имен с заданным именем уже существует, оно не будет создано заново:

var MYAPP = MYAPP || {}; 
MYAPP.namespace = function (ns_string) { 
            var parts = ns_string.split('.'), 
           parent = MYAPP, 
           i; 
          // отбросить начальный префикс - имя глобального объекта 
         if (partsf[0] === "MYAPP") { 
         parts = parts.slice(1); 
         } 
         for (i = 0; i < parts.length; i += 1) { 
             // создать свойство, если оно отсутствует 
            if (typeof parent[parts[i]] === "undefined") { 
                parent[parts[i]] = {}; 
             } 
            parent = parent[parts[i]]; 
        } 
        return parent; 
}; 

Такая реализация делает допустимыми все следующие варианты использования функции:

// присваивать возвращаемое значение локальной переменной 
var module2 = MYAPP.namespace(‘MYAPP.modules.module2’); 
module2 === MYAPP.modules.module2; // true 
// опускать начальный префикс 'MYAPP' 
MYAPP.namespace('modules.module51'); 
// создавать глубоко вложенные пространства имен 
MYAPP.namespace('once.upon.a.time.there.was.this.long.nested.property' ); 

Часть 3.2 Шаблон “Частные свойства и методы”

В языке JavaScript нет специальных средств объявления частных (private), защищенных (protected) или общедоступных (public) свойств и методов, как в языке Java или в других языках. Все члены объектов в этом языке являются общедоступными:

var myobj = { 
                  myprop: 1, 
                 getProp: function () { 
                          return this.myprop; 
                  } 
}; 
console.log(myobj.myprop); // 'myprop' - общедоступный член 
console.log(myobj.getPropO); // getPropO - также общедоступный член 

To же справедливо и при использовании функций-конструкторов для создания объектов - все члены являются общедоступными:

function Gadget() { 
          this.name = 'iPod'; 
          this.stretch = function () { 
                    return 'iPad'; 
         }; 
} 
var toy = new GadgetO; 
console.log(toy.name); // 'name' - общедоступный член 
console.log(toy.stretch()); // stretch() - общедоступный член 

Несмотря на отсутствие в языке специальных средств определения частных членов, их все-таки можно создать, используя для этого замыкания. Функция-конструктор может образовывать замыкание, и любые переменные, ставшие частью этого замыкания, не будут доступны за пределами объекта. Однако такие частные члены останутся доступными для общедоступных методов - методов, определяемых внутри конструктора и являющихся частью интерфейса возвращаемого объекта. Давайте рассмотрим пример создания частного члена, недоступного за пределами объекта:

function Gadget() { 
         // частный член 
          var name = ‘iPod’; 
        // общедоступная функция 
         this.getName = function () { 
                return name; 
         }; 
}
var toy = new Gadget(); 
// имя 'name' не определено, частный член 
console.log(toy.папе); // undefined 
// общедоступный метод может обратиться к частному члену 'name' 
console.log(toy.getName()); // "iPod" 

Как видите, в JavaScript можно легко создавать частные члены. Все, что для этого необходимо, - обернуть данные, которые вы хотели бы оставить частными, в функцию, сделав их локальными по отношению к этой функции и, следовательно, недоступными за пределами функции.

До сих пор все примеры реализации частных свойств, которые мы видели, были основаны на использовании конструкторов. А как быть в случаях, когда объекты определяются в виде литералов? Возможно ли в таких ситуациях создавать частные члены? Как вы уже видели, чтобы обеспечить сокрытие данных, их необходимо обернуть функцией. Так, в случае литералов объектов замыкание можно создать с помощью дополнительной анонимной функции, вызываемой немедленно. Например:

var myobj; // это будет объект 
(function () { 
          // частные члены 
          var name = "my, oh my"; 
          // реализация общедоступных членов 
          // обратите внимание на отсутствие инструкции 'var' 
           myobj = { 
                // привилегированный метод 
               getName: function () { 
                      return name; 
                } 
          }; 
}()); 
myobj.getName(); // "my, oh my" 

Та же идея положена в основу следующего примера, имеющего несколько иную реализацию:

var myobj = (function () { 
           // частные члены 
         var name = "my, oh my"; 
          // реализация общедоступных членов 
         return { 
                getName: function () { 
                        return name; 
               } 
}; 
}()); 
myobj.getName(); // "my, oh my" 

Этот шаблон является также основой шаблона, известного под названием «модуль», исследованием которого мы займемся чуть ниже.

Один из недостатков создания частных членов с применением конструкторов заключается в том, что они создаются всякий раз, когда вызывается конструктор для создания нового объекта.

Фактически эта проблема относится ко всем членам, добавляемым внутри конструкторов. Чтобы сэкономить свои усилия и память, общие для всех экземпляров свойства и методы можно добавить в свойство prototype конструктора. При таком подходе общие члены будут совместно использоваться всеми экземплярами, созданными с помощью одного и того же конструктора. Аналогичным образом можно определять частные свойства, совместно используемые всеми экземплярами. Для этого необходимо применить комбинацию из двух шаблонов: частные свойства внутри конструкторов и частные свойства в литералах объектов. Так как свойство prototype является обычным объектом, его можно определить в виде литерала.

Как это сделать, показано в следующем примере:

function Gadget() { 
          // частный член 
          var name = 'iPod'; 
          // общедоступная функция 
           this.getName = function () { 
                 return name; 
          }; 
} 
Gadget.prototype = (function () { 
           // частный член 
          var browser = "Mobile Webkit"; 
          // общедоступные члены прототипа 
          return { 
                getBrowser: function () { 
                      return browser; 
               } 
         }; 
}()); 
var toy = new Gadget(); 
console.log(toy.getName()); // "собственный" привилегированный метод 
console.log(toy.getBrowser()); // привилегированный метод прототипа