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

Модули CommonJS

Возможно, вы что-то слышали о CommonJS за последние пару лет. CommonJS — это добровольная рабочая группа, которая проектирует, проектирует и стандартизирует различные JavaScript API. На сегодняшний день они ратифицировали стандарты для модулей и пакетов — CommonJS определяют простой API для написания модулей, которые могут быть использованы в браузере с помощью тега <script>, как с синхронной, так и с асинхронной загрузкой. Реализация паттерна «модуль» с помощью CommonJS выглядит очень просто, и я нахожу это уверенным шагом на пути к модульной системе, предложенной в ES Harmony (следующей версии JavaScript).

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

/*
Пример обеспечения совместимости между AMD и обычным CommonJS с помощью
создания обертки над последним:
*/
 
(function(define) {
define(function(require,exports) {
  // module contents
  var dep1 = require("dep1");
  exports.someExportedFunction = function() {...};
  //...
});
})(typeof define=="function"?define:function(factory){factory(require,exports)});

Есть много хороших JavaScript-библиотек, для загрузки модулей в формате CommonJS, но моим личным предпочтением является RequireJS. Полный учебник по RequireJS выходит за рамки этого руководства, но я могу порекомендовать вам почитать пост Джеймса Брука «ScriptJunkie». Кроме того, я знаю многих людей, которые предпочитают Yabble.

Из коробки, RequireJS уже содержит методы для простого создания статичных модулей с обертками. Благодаря этим методам становится действительно легко создавать модули с поддержкой асинхронной загрузки. RequireJS может легко загружать модули и их зависимости, выполняя тело модуля, сразу, как только это становится возможным.

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

var encodeToASCII = require("encoder").encodeToASCII;
exports.encodeSomeSource = function() {
  // Обработка изображения, затем вызов encodeToASCII
}

Этот код не будет работать с тегом script. Ему необходим определенный контекст. Я имею в виду наш метод encodeToASCII, который ссылается на несуществующие в контексте window методы require и exports. В такой ситуации нам пришлось бы писать require и exports для каждого отдельного модуля. Эту проблему легко решают клиентские библиотеки, которые загружают скрипты через XHR-запросы, а затем выполняют eval().

Попробуем переписать этот модуль, используя RequireJS:

define(function(require, exports, module) {
  var encodeToASCII = require("encoder").encodeToASCII;
  exports.encodeSomeSource = function() {
    // Обработка изображения, затем вызов encodeToASCII
  }
});

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

Паттерн «Фасад»

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

Как правило, фасад используется для создания некоторой абстракции, скрывающей за собой совершенно иную реальность. Паттерн «фасад» обеспечивает удобный высокоуровневый интерфейс для больших блоков кода, скрывая за собой их истинную сложность. Относитесь к фасаду, как к упрощенному API, который вы отдаете в пользование другим разработчикам.

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

Благодаря такому подходу, пользователь взаимодействует только с интерфейсом, не имея никакого представления о подсистемах, которые скрываются за ним.

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

Надежный фасад — наш упрощенный интерфейс — позволит нам не беспокоиться о тесных связях некоторых модулей нашей системы с dojo, jQuery, YUI, zepto или какой-либо другой библиотекой. Это становится не так важно. Вы можете переходить с одной библиотеки на другую не меняя слой взаимодействия. К примеру, с jQuery на dojo. Более того, у вас появляется возможность совершить такой переход на более поздних этапах, без изменений в остальных частях системы.

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

var module = (function() {
  var _private = {
    i: 5,
    get: function() {
      console.log('Текущее значение:' + this.i);
    },
    set: function(val) {
      this.i = val;
    },
    run: function() {
      console.log('процесс запущен');
    },
    jump: function() {
      console.log('резкое изменение');
    }
  };
  return {
    facade: function(args) {
      _private.set(args.val);
      _private.get();
      if (args.run) {
        _private.run();
      }
    }
  }
}());
 
module.facade({run:true, val:10}); // Текущее значение: 10, процесс запущен

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

Авторизация