Паттерны для масштабируемых JavaScript-приложений ч.4

Паттерн «Модуль»

«Модуль» — это популярная реализация паттерна, инкапсулирующего приватную информацию, состояние и структуру, используя замыкания. Это позволяет оборачивать публичные и приватные методы и переменные в модули, и предотвращать их попадание в глобальный контекст, где они могут конфликтовать с интерфейсами других разработчиков. Паттерн «модуль» возвращает только публичную часть API, оставляя всё остальное доступным только внутри замыканий.

Это хорошее решение для того, чтобы скрыть внутреннюю логику от посторонних глаз и производить всю тяжелую работу исключительно через интерфейс, который вы определите для использования в других частях вашего приложения. Этот паттерн очень похож на немедленно-вызываемые функции (IIFE), за тем исключением, что модуль вместо функции, возвращает объект.

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

Ниже вы можете увидеть корзину покупок, реализованную с помощью паттерна «модуль». Получившийся компонент находится в глобальном объекте basketModule, и содержит всё, что ему необходимо. Находящийся внутри него, массив basket приватный, и другие части вашего приложения не могут напрямую взаимодействовать с ним. Массив basket существует внутри замыкания, созданного модулем, и взаимодействовать с ним могут только методы, находящиеся в том же контексте (например, addItem(), getItem()).

var basketModule = (function() {
  var basket = []; // приватная переменная
    return { // методы доступные извне
        addItem: function(values) {
            basket.push(values);
        },
        getItemCount: function() {
            return basket.length;
        },
        getTotal: function() {
           var q = this.getItemCount(),p=0;
            while(q--){
                p+= basket[q].price; 
            }
            return p;
        }
    }
}());

Внутри модуля, как вы заметили, мы возвращаем объект. Этот объект автоматически присваивается переменной basketModule, так что с ним можно взаимодействовать следующим образом:

// basketModule - это объект со свойствами, которые могут также быть и методами:
basketModule.addItem({item:'bread', price:0.5});
basketModule.addItem({item:'butter', price:0.3});
 
console.log(basketModule.getItemCount());
console.log(basketModule.getTotal());
 
// А следующий ниже код работать не будет:
console.log(basketModule.basket); // undefined потому что не входит в возвращаемый объект
console.log(basket); // массив доступен только из замыкания

Методы выше, фактически, помещены в неймспейс basketModule.

Исторически, паттерн «модуль» был разработан в 2003 году группой людей, в число которых входил Ричард Корнфорд. Позднее, этот паттерн был популяризован Дугласом Крокфордом в его лекциях, и открыт заново в блоге YUI благодаря Эрику Мирагилиа.

Давайте посмотрим на реализацию «модуля» в различных библиотеках и фреймворках.

Dojo

Dojo старается обеспечивать поведение похожее на классы с помощью dojo.declare, который, кроме создания «модулей», также используется и для других вещей. Давайте попробуем, для примера, определить basket как модуль внутри неймспейса store:

// традиционный способ
    var store = window.store || {};
    store.basket = store.basket || {};
 
    // с помощью dojo.setObject
    dojo.setObject("store.basket.object", (function() {
        var basket = [];
        function privateMethod() {
            console.log(basket);
        }
        return {
            publicMethod: function() {
                privateMethod();
            }
        };
    }()));

Лучшего результата можно добиться, используя dojo.provide и миксины.

YUI

Следующий код, по большей части, основан на примере реализации паттерна «модуль» в фреймворке YUI, разработанным Эриком Миргалиа, но более самодокументирован.

YAHOO.store.basket = function () {
 
        // приватная переменная:
        var myPrivateVar = "Ко мне можно получить доступ только из YAHOO.store.basket.";
 
        // приватный метод:
        var myPrivateMethod = function() {
            YAHOO.log("Я доступен только при вызове из YAHOO.store.basket");
        }
 
        return {
            myPublicProperty: "Я - публичное свойство",
            myPublicMethod: function() {
                YAHOO.log("Я - публичный метод");
 
                // Будучи внутри корзины я могу получить доступ к приватным переменный и методам:
                YAHOO.log(myPrivateVar);
                YAHOO.log(myPrivateMethod());
 
                // Родной контекст метода myPublicMethod сохранён
                // поэтому мы имеет доступ к this
                YAHOO.log(this.myPublicProperty);
            }
        };
 
    }();
jQuery

Существует множество способов, чтобы представить jQuery-код в виде паттерна «модуль», даже если этот код не напоминает привычные jQuery-плагины. Бен Черри ранее предлагал способ, при котором, если у модулей есть общие черты, то они объявляются через функцию-обертку.

В следующем примере функция library используется для объявления новой библиотеки и, автоматически, при создании библиотеки (т.е. модуля), связывает вызов метода init с document.ready.

function library(module) {
      $(function() {
        if (module.init) {
          module.init();
        }
      });
      return module;
    }
 
    var myLibrary = library(function() {
       return {
         init: function() {
           /* код модуля */
         }
       };
    }());

Литеральная нотация объектов

В литеральной нотации объект описывается внутри блока фигурных скобок ({}), как набор разделенных запятой пар ключ/значение. Ключи объекта могут быть как строками, так и идентификаторами. После имени ставится двоеточие. В объекте не должно стоять запятой после последней пары ключ/значение, так как это может привести к ошибкам.

Литерал объекта не требует использования оператора new для создания экземпляра, но он не должен стоять в начале выражения, так как открытая { может быть воспринята как начало блока. Ниже вы можете увидеть пример модуля, определенного с помощью литеральной нотации объекта. Новые члены объекта могут быть добавлены с помощью конструкции myModule.property = 'someValue';

Паттерн «модуль» может быть полезен для многих вещей. Но если вы считаете, что вам не нужно делать приватными некоторые методы или свойства, то литерал объекта — более чем подходящий выбор.
var myModule = {
    myProperty: 'someValue',
    // Литералы объектов могут содержать свойства и методы.
    // ниже в свойстве определен другой объект,
    // для описания конфигурации:
    myConfig: {
        useCaching: true,
        language: 'en'
    },
    // Очень простой метод
    myMethod: function() {
        console.log('I can haz functionality?');
    },
    // вывод значения заданного в конфигурации
    myMethod2: function() {
        console.log('Caching is: ' + ((this.myConfig.useCaching) ? 'enabled' : 'disabled'));
    },
    // переопределение конфигурации
    myMethod3: function(newConfig) {
        if (typeof newConfig == 'object') {
            this.myConfig = newConfig;
            console.log(this.myConfig.language); 
        }
    }
};
 
myModule.myMethod();  // 'I can haz functionality'
myModule.myMethod2(); // Вывод 'enabled'
myModule.myMethod3({language:'fr',useCaching:false}); // 'fr'

Авторизация