Сегодня мы собираемся надеть шляпы по информатике, когда узнаем о некоторых общих шаблонах проектирования. Шаблоны проектирования предлагают разработчикам способы решения технических проблем многократно и элегантно. Хотите стать лучшим разработчиком JavaScript? Тогда читайте дальше.
Каждые несколько недель мы пересматриваем некоторые из любимых постов нашего читателя на протяжении всей истории сайта. Этот учебник был впервые опубликован в июле 2012 года.
Вступление
Твердые шаблоны проектирования являются основным строительным блоком для поддерживаемых программных приложений. Если вы когда-либо участвовали в техническом интервью, вам понравились вопросы о них. В этом уроке мы рассмотрим несколько шаблонов, которые вы можете начать использовать сегодня.
Что такое шаблон дизайна?
Шаблон проектирования — это многоразовое программное решение.
Проще говоря, шаблон проектирования — это многоразовое программное решение для определенного типа проблем, которые часто возникают при разработке программного обеспечения. За долгие годы практики разработки программного обеспечения эксперты нашли способы решения подобных проблем. Эти решения были включены в шаблоны проектирования. Так:
- шаблоны являются проверенными решениями проблем разработки программного обеспечения
- шаблоны масштабируемы, так как они обычно структурированы и имеют правила, которым вы должны следовать
- шаблоны могут быть использованы для аналогичных задач
Далее мы рассмотрим несколько примеров шаблонов проектирования.
Типы шаблонов дизайна
При разработке программного обеспечения шаблоны проектирования обычно группируются в несколько категорий. Мы рассмотрим три наиболее важных из этого урока. Они объясняются вкратце ниже:
- Шаблоны создания ориентированы на способы создания объектов или классов. Это может показаться простым (и это в некоторых случаях), но крупные приложения должны контролировать процесс создания объекта.
- Шаблоны структурного проектирования фокусируются на способах управления отношениями между объектами, так что ваше приложение проектируется масштабируемым образом. Ключевым аспектом структурных шаблонов является гарантия того, что изменение одной части вашего приложения не повлияет на все остальные части.
- Поведенческие модели сосредоточены на коммуникации между объектами.
У вас могут остаться вопросы после прочтения этих кратких описаний. Это естественно, и все прояснится, как только мы рассмотрим некоторые шаблоны проектирования ниже. Так что читайте дальше!
Примечание о классах в JavaScript
Читая о шаблонах проектирования, вы часто будете видеть ссылки на классы и объекты. Это может сбивать с толку, так как JavaScript не имеет конструкции «класс»; более правильный термин — «тип данных».
Типы данных в 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. Я призываю вас рассмотреть его в качестве справочного материала, когда вы начнете свой следующий проект.