Дизайн Javascript кода. Фасад

В одной из своих прошлых записей я рассказывал о паттерне «Модуль», а также о том почему его стоит использовать при написании Javascript кода. Данный пост посвящен другому структурному паттерну, который называется «Фасад». Он широко используется в библиотеках и фреймворках, написанных на Javascript, и обладает неоспоримыми достоинствами, о которых я и постараюсь рассказать ниже.

Почему «Фасад» стоит использовать

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

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

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

Во-вторых он позволяет добиться меньшей связанности компонентов системы. Имея постоянный слой взаимодействия, которым является «Фасад», мы можем менять используемые за ним библиотеки без каких-либо проблем. Это вопрос становится особенно актуальным когда вы не уверены в используемой библиотеке и в дальнейшем планируете перейти на другую. К примеру с jQuery на dojo.

«Фасад» на практике

Далее я приведу пример использования паттерна «Фасад». Допустим нам необходимо реализовать функционал для работы с географической картой. Первое что приходит на ум - это использовать Google Maps API или API Яндекс Карт. Каждый из них имеет свои достоинства и недостатки, а также различную внутреннюю реализацию. В данном случае нам-то и пригодится «Фасад». Давайте абстрагируемся от конкретного функционала и создадим удобный интерфейс для работы с картой.

function MapFacade() {
  this.getMap = function(id) { 
    /* Вызов необходимого API */ 
  };
   
  this.placeMarker = function(map, lat, lng) { 
    /* Вызов необходимого API */ 
  };
};

Конкретные реализации API карт будут инкапсулированы в модулях.

var gmap = {
  loadMap: function(id, lat, lng) {
    return new google.maps.Map(document.getElementById(id), {
      center: this.getLatLng(lat, lng),
      zoom: 15
    });
  },
   
  putMarker: function(map, lat, lng) {
 	var marker = this.getMarker(lat, lng);
 	marker.setMap(map);
  },
   
  getLatLng: function (lat, lng) {
    return new google.maps.LatLng(lat, lng);
  },
   
  getMarker: function (lat, lng) {
    return new google.maps.Marker({
      position: this.getLatLng(lat, lng)
    });
  },
};
 
var ymap = {
  loadMap: function(id, lat, lng) {
    return new ymaps.Map(id, {
      center: [lat, lng],
      zoom: 15
    });
  },
   
  putPlacemark: function(map, lat, lng) {
    var placemark = this.getPlacemark(lat, lng);
    map.geoObjects.add(placemark);
  },
   
  getPlacemark: function(lat, lng) {
    return new ymaps.Placemark([lat, lng]);
  }
};

Теперь достаточно подключить необходимый модуль к созданному ранее интерфейсу MapFacade.

function MapFacade() {
  this.getMap = function(id) { 
    return gmap.loadMap(id, 53.90, 27.57);
  };
   
  this.placeMarker = function(map, lat, lng) { 
    gmap.putMarker(map, lat, lng); 
  };
};
 
var mapFacade = new MapFacade();
var map = mapFacade.getMap('map-canvas');
mapFacade.placeMarker(map, 53.90, 27.57);

или

function MapFacade() {
  this.getMap = function(id) { 
    return ymap.loadMap(id, 53.90, 27.57);
  };
   
  this.placeMarker = function(map, lat, lng) { 
    ymap.putPlacemark(map, lat, lng); 
  };
};
 
var mapFacade = new MapFacade();
var map = mapFacade.getMap('map-canvas');
mapFacade.placeMarker(map, 53.90, 27.57); 

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