Статьи

Понимание шаблонов проектирования в JavaScript

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

Переизданный учебник

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


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


Шаблон проектирования — это многоразовое программное решение.

Проще говоря, шаблон проектирования — это многоразовое программное решение для определенного типа проблем, которые часто возникают при разработке программного обеспечения. За долгие годы практики разработки программного обеспечения эксперты нашли способы решения подобных проблем. Эти решения были включены в шаблоны проектирования. Так:

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

Далее мы рассмотрим несколько примеров шаблонов проектирования.


При разработке программного обеспечения шаблоны проектирования обычно группируются в несколько категорий. Мы рассмотрим три наиболее важных из этого урока. Они объясняются вкратце ниже:

  1. Шаблоны создания ориентированы на способы создания объектов или классов. Это может показаться простым (и это в некоторых случаях), но крупные приложения должны контролировать процесс создания объекта.

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

  3. Поведенческие модели сосредоточены на коммуникации между объектами.

У вас могут остаться вопросы после прочтения этих кратких описаний. Это естественно, и все прояснится, как только мы рассмотрим некоторые шаблоны проектирования ниже. Так что читайте дальше!


Читая о шаблонах проектирования, вы часто будете видеть ссылки на классы и объекты. Это может сбивать с толку, так как JavaScript не имеет конструкции «класс»; более правильный термин — «тип данных».

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

01
02
03
04
05
06
07
08
09
10
11
function Person(config) {
    this.name = config.name;
    this.age = config.age;
}
 
Person.prototype.getAge = function() {
    return this.age;
};
 
var tilo = new Person({name:»Tilo», age:23 });
console.log(tilo.getAge());

Обратите внимание на использование prototype при определении методов для типа данных Person . Поскольку несколько объектов Person будут ссылаться на один и тот же прототип, это позволяет использовать getAge() для всех экземпляров типа данных Person , а не переопределять его для каждого экземпляра. Кроме того, любой тип данных, который наследуется от Person будет иметь доступ к getAge() .

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var retinaMacbook = (function() {
 
    //Private variables
    var RAM, addRAM;
 
    RAM = 4;
 
    //Private method
    addRAM = function (additionalRAM) {
        RAM += additionalRAM;
    };
 
    return {
 
        //Public variables and methods
        USB: undefined,
        insertUSB: function (device) {
            this.USB = device;
        },
 
        removeUSB: function () {
            var device = this.USB;
            this.USB = undefined;
            return device;
        }
    };
})();

В приведенном выше примере мы создали объект retinaMacbook с открытыми и закрытыми переменными и методами. Вот как мы будем использовать это:

1
2
3
retinaMacbook.insertUSB(«myUSB»);
console.log(retinaMacbook.USB);
console.log(retinaMacbook.RAM) //logs out undefined

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


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

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

Применение шаблона построителя позволяет нам создавать объекты, указывая только тип и содержимое объекта. Нам не нужно явно создавать объект.

Например, вы, вероятно, делали это бесчисленное количество раз в jQuery:

1
2
3
4
5
6
7
8
var myDiv = $(‘<div id=»myDiv»>This is a div.</div>’);
 
//myDiv now represents a jQuery object referencing a DOM node.
 
var someText = $(‘<p/>’);
//someText is a jQuery object referencing an HTMLParagraphElement
 
var input = $(‘<input />’);

Взгляните на три примера выше. В первом мы передали элемент <div/> с некоторым содержанием. Во втором мы передали пустой <p> . В последнем мы передали элемент <input /> . Результат всех трех был одинаковым: нам был возвращен объект jQuery, ссылающийся на узел DOM.

Переменная $ принимает шаблон Builder в jQuery. В каждом примере нам возвращали объект DOM jQuery, и у нас был доступ ко всем методам, предоставляемым библиотекой jQuery, но ни в коем случае мы явно не вызывали document.createElement . Библиотека JS обрабатывает все это под капотом.

Представьте, сколько бы работы потребовалось бы, если бы нам пришлось явно создать элемент DOM и вставить в него содержимое! Используя шаблон компоновщика, мы можем сосредоточиться на типе и содержимом объекта, а не на явном его создании.

Ранее мы рассмотрели, как определять типы данных в JavaScript с помощью функций и добавлять методы в prototype объекта. Шаблон Prototype позволяет объектам наследоваться от других объектов через свои прототипы.

Шаблон прототипа — это шаблон, в котором объекты создаются на основе шаблона существующего объекта посредством клонирования.

Это простой и естественный способ реализации наследования в JavaScript. Например:

01
02
03
04
05
06
07
08
09
10
11
12
var Person = {
    numFeet: 2,
    numHeads: 1,
    numHands:2
};
 
//Object.create takes its first argument and applies it to the prototype of your new object.
var tilo = Object.create(Person);
 
console.log(tilo.numHeads);
tilo.numHeads = 2;
console.log(tilo.numHeads) //outputs 2

Свойства (и методы) объекта Person применяются к прототипу объекта tilo . Мы можем переопределить свойства объекта tilo если хотим, чтобы они были другими.

В приведенном выше примере мы использовали Object.create() . Однако Internet Explorer 8 не поддерживает более новый метод. В этих случаях мы можем смоделировать его поведение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var vehiclePrototype = {
 
  init: function (carModel) {
    this.model = carModel;
  },
 
  getModel: function () {
    console.log( «The model of this vehicle is » + this.model);
  }
};
 
 
function vehicle (model) {
 
  function F() {};
  F.prototype = vehiclePrototype;
 
  var f = new F();
 
  f.init(model);
  return f;
 
}
 
var car = vehicle(«Ford Escort»);
car.getModel();

Единственным недостатком этого метода является то, что вы не можете указывать свойства только для чтения, которые можно указать при использовании Object.create() . Тем не менее, шаблон прототипа показывает, как объекты могут наследоваться от других объектов.


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

Составной шаблон — это еще один шаблон, который вы, вероятно, использовали раньше без какой-либо реализации.

Составной шаблон говорит, что группу объектов можно обрабатывать так же, как отдельный объект группы.

Так что это значит? Хорошо, рассмотрим этот пример в jQuery (большинство библиотек JS будет иметь эквивалент):

01
02
03
04
05
06
07
08
09
10
11
$(‘.myList’).addClass(‘selected’);
$(‘#myItem’).addClass(‘selected’);
 
//dont do this on large tables, it’s just an example.
$(«#dataTable tbody tr»).on(«click», function(event){
    alert($(this).text());
});
 
$(‘#myButton’).on(«click», function(event) {
    alert(«Clicked.»);
});

Большинство библиотек JavaScript предоставляют согласованный API независимо от того, имеем ли мы дело с одним элементом DOM или массивом элементов DOM. В первом примере мы можем добавить selected класс ко всем элементам, выбранным селектором .myList , но мы можем использовать тот же метод при работе с единственным элементом DOM, #myItem . Точно так же мы можем прикрепить обработчики событий, используя метод on() на нескольких узлах или на одном узле через один и тот же API.

Используя шаблон Composite, jQuery (и многие другие библиотеки) предоставляют нам упрощенный API.

Составной шаблон может иногда вызывать проблемы. В свободно типизированном языке, таком как JavaScript, часто бывает полезно узнать, имеем ли мы дело с одним или несколькими элементами. Поскольку в составном шаблоне используется один и тот же API для обоих, мы часто можем принять одно за другое и в результате получить неожиданные ошибки. Некоторые библиотеки, такие как YUI3, предлагают два отдельных метода получения элементов ( Y.one() и Y.all() ).

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

Шаблон фасада предоставляет пользователю простой интерфейс, скрывая его сложность.

Шаблон Facade почти всегда улучшает удобство использования программного обеспечения. Снова используя jQuery в качестве примера, одним из наиболее популярных методов библиотеки является метод ready() :

1
2
3
4
5
$(document).ready(function() {
 
    //all your code goes here…
 
});

Метод ready() фактически реализует фасад. Если вы посмотрите на источник, вот что вы найдете:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
ready: (function() {
 
    …
 
    //Mozilla, Opera, and Webkit
    if (document.addEventListener) {
        document.addEventListener(«DOMContentLoaded», idempotent_fn, false);
        …
    }
    //IE event model
    else if (document.attachEvent) {
 
        // ensure firing before onload;
        document.attachEvent(«onreadystatechange», idempotent_fn);
 
        // A fallback to window.onload, that will always work
        window.attachEvent(«onload», idempotent_fn);
 
        …
    }
 
})

В самом деле, метод ready() не так прост. jQuery нормализует несоответствия браузера, чтобы гарантировать, что ready() запускается в соответствующее время. Тем не менее, как разработчик, вам представлен простой интерфейс.

Большинство примеров шаблона Фасад следуют этому принципу. При его реализации мы обычно полагаемся на условные операторы, но представляем их как простой интерфейс для пользователя. Другие методы, реализующие этот шаблон, включают animate() и css() . Можете ли вы подумать, почему они будут использовать фасадный рисунок?


Любые объектно-ориентированные программные системы будут иметь связь между объектами. Отказ от организации такого общения может привести к ошибкам, которые трудно найти и исправить. Поведенческие шаблоны проектирования предписывают различные методы организации общения между объектами. В этом разделе мы рассмотрим паттерны Observer и Mediator.

Модель Observer — это первая из двух моделей поведения, которые мы собираемся пройти. Вот что это говорит:

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

Звучит довольно просто, правда? Нам нужно три метода для описания этого шаблона:

  • publish(data) : вызывается субъектом, когда у него есть уведомление, которое необходимо сделать. Некоторые данные могут быть переданы этим методом.
  • subscribe(observer) : вызывается субъектом, чтобы добавить наблюдателя в свой список наблюдателей.
  • unsubscribe(observer) : вызываемый субъектом удалить наблюдателя из своего списка наблюдателей.

Что ж, получается, что большинство современных библиотек JavaScript поддерживают эти три метода как часть их инфраструктуры пользовательских событий. Обычно есть метод on() или attach() метод trigger() или fire() метод off() или detach() . Рассмотрим следующий фрагмент:

1
//We just create an association between the jQuery events methods
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//and those prescribed by the Observer Pattern but you don’t have to.
var o = $( {} );
$.subscribe = o.on.bind(o);
$.unsubscribe = o.off.bind(o);
$.publish = o.trigger.bind(o);
 
// Usage
document.on( ‘tweetsReceived’, function(tweets) {
    //perform some actions, then fire an event
 
    $.publish(‘tweetsShow’, tweets);
});
 
//We can subscribe to this event and then fire our own event.
$.subscribe( ‘tweetsShow’, function() {
    //display the tweets somehow
    ..
 
    //publish an action after they are shown.
    $.publish(‘tweetsDisplayed);
});
 
$.subscribe(‘tweetsDisplayed, function() {
    …
});

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

Последним паттерном, который мы собираемся рассмотреть, является паттерн медиатора. Это похоже на шаблон Observer, но с некоторыми заметными отличиями.

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

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

В этом гистограмме есть упрощенная реализация шаблона Mediator от Addy Osmani. Давайте поговорим о том, как вы можете его использовать. Представьте, что у вас есть веб-приложение, которое позволяет пользователям нажимать на альбом и воспроизводить из него музыку. Вы можете установить посредника следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$(‘#album’).on(‘click’, function(e) {
    e.preventDefault();
    var albumId = $(this).id();
    mediator.publish(«playAlbum», albumId);
});
 
 
var playAlbum = function(id) {
    …
    mediator.publish(«albumStartedPlaying», {songList: [..], currentSong: «Without You»});
 
};
 
var logAlbumPlayed = function(id) {
    //Log the album in the backend
};
 
var updateUserInterface = function(album) {
    //Update UI to reflect what’s being played
};
 
//Mediator subscriptions
mediator.subscribe(«playAlbum», playAlbum);
mediator.subscribe(«playAlbum», logAlbumPlayed);
mediator.subscribe(«albumStartedPlaying», updateUserInterface);

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

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


Кто-то уже успешно применял это в прошлом.

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


Большая часть содержания этой статьи может быть найдена в превосходной книге « Шаблоны проектирования JavaScript для обучения » Эдди Османи. Это онлайн-книга, выпущенная бесплатно по лицензии Creative Commons. Книга подробно описывает теорию и реализацию множества различных шаблонов, как в ванильном JavaScript, так и в различных библиотеках JS. Я призываю вас рассмотреть его в качестве справочного материала, когда вы начнете свой следующий проект.