Паттерн Observer и Pub/Sub

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

Observer

Observer, переводя на русский язык, означает “Наблюдатель”. Логично предположить, что если есть наблюдатель, то должен быть и предмет его наблюдения. Например, человек, который увлекается рыбалкой, постоянно является наблюдателем, когда смотрит на неподвижный поплавок. Естественно, целью наблюдения за поплавком является событие, которое заставит предпринять какое-либо действие.

Переводя описанное выше на технические термины, можно выделить следующие детали. Паттер “Observer” полезен, когда:

  • Есть наблюдаемый объект (subject) - в нашем случае - поплавок;
  • Есть наблюдатель (object) - в нашем случае - рыбак;
  • Есть событие (event) - в нашем случае - поплавок дергается;
  • Есть действие (event handler) - в нашем случае - действие рыбака (предположим, он дергает удачку).

Другими словами, использование паттерна нужно, когда 2 независимых объекта должны быть связаны между собой посредством какого-нибудь события.

Чуть выше, я говорил о том, что многие не представляют, что уже постоянно используют этот подход. Посмотрим на пример:

1
2
3
$('button').on('click', function(){
alert('button clicked');
});

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

  • Subject в данном случае - кнопка (button);
  • Object - документ;
  • Event, очевидно, клик;
  • Event Handler - функция, которая будет вызвана при возникновении события.

Pub/Sub

По сути Pub/Sub это отдельно взятая реализация Observer. Можно сказать, подмножество или прием. Суть в нем та же самая. Есть небольшие различия в терминологии, но чтобы не забивать голову терминами, я не стану их тут приводить. Название Pub/Sub - это сокращение от Publisher/Subscriber (Издатель/Подписчик). Так на самом деле легче всего запомнить этот паттерн.

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

Вот, пожалуй что и готовый пример для программирования.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//Опишем объект "Издатель"
function Publisher(){
//это просто массив для хранения всех подписчиков
this.subscribers = [];
}

//Опишем метод, который позволит подписчикам, подписаться на издателя
//грубо говоря, любой желающий может пойти на почту и подписаться
//или как там это сейчас делается? :)
Publisher.prototype.subscribe = function(onPublish){
this.subscribers.push(onPublish);
};

//Опишем метод, который будет вызываться, когда издатель издал новую новость
Publisher.prototype.publish = function(sometext){
//пробегаем по всем подписчикам и отдаем ему текст новости
this.subscribers.forEach(function(subscriber){
subscriber(sometext);
});
};


//Ну а теперь создадим два новых издательства
var freedom = new Publisher();
var union = new Publisher();

//Теперь Сашу, который всем обо всем рассказывает по каждому поводу
var Sasha = {
tellToEveryone: function(news){
console.log('OMG! Did you hear that ' + news);
}
};

//Теперь Машу, которая обо всем пишет свои мысли в блог
var Masha = {
writeToBlog: function(news){
console.log('My opinions about ' + news);
}
};

//Осуществим подписку Саши и Маши на газету Freedom

//строго говоря, мы могли бы написать вот так:
//freedom.subscribe(function(news){
// Sasha.tellToEveryone(news);
//});

//но более аккуратной будет все же именно такая запись:
freedom.subscribe(Sasha.tellToEveryone);
freedom.subscribe(Masha.writeToBlog);
//а Машу подпишем еще и на журнал Union
union.subscribe(Masha.writeToBlog);

//а теперь Издательства публикуют свои новости
freedom.publish('The winter is coming!');
union.publish("It's snowball time!");

//В консоль будет выведено:
//OMG! Did you hear that The winter is coming!
//My opinions about The winter is coming!
//My opinions about It's snowball time!

Поиграться с живым примером, можно в JS Fiddle.

Хочу добавить, что я привел очень простой пример паттерна, чтобы не захламлять код терминологией и дополнительными методами. Но, по-хорошему, у издателя должен быть реализован метод по отписке (unsubscribe) наблюдателей. И правда, что если взгляды Саши изменятся, и он решит отписаться от газеты Freedom и начать читать только журнал Union?

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