Паттерн Observer — «JavaScript Design Patterns, 2017» Эдди Османи. Перевод: часть 6.1

Оригинальный текст — https://addyosmani.com/
Не забудьте про вторую часть.


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

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

Часто бывает полезно обращаться к исходным определениям паттернов (которые в свою очередь являются частью мира Language agnostic), чтобы получить более широкое представление о преимуществах и их использовании. Определение паттерна Observer в книге «GoF», Design Patterns: Elements of Reusable Object-Oriented Software:

 

«Интересующийся состоянием субъекта наблюдатель (наблюдателей может быть любое количество) подписываются к нему. Когда что-то меняется в нашем субъекте, наблюдателю отправляется уведомление, которое вызывает метод обновления у каждого «подписчика». Когда наблюдателя больше не интересует состояние субъекта, то он отписывается от него».

 

Давайте теперь определим основных участников работы нашего нового паттерна:

  • Subject — он же субъект: хранит информацию о своих наблюдателях и позволяет их добавлять и удалять.
  • Observer  — он же наблюдатель: определяет интерфейс обновления для объектов, которые должны быть уведомлены об изменении состояния субъекта.
  • ConcreteSubject — он же конкретный субъект: посылает уведомления наблюдателям об изменение состояния, сохраняет состояние ConcreteObservers
  • ConcreteObserver — он же конкретный наблюдатель: хранит ссылку на ConcreteSubject, реализует интерфейс обновления для Observer, чтобы поддержать согласованность с субъектом.

 

Для начала давайте смоделируем список зависимых наблюдателей, которые могут иметь субъект:

Затем смоделируем Субъект, а также возможность добавлять, удалять, уведомлять наблюдателей из списка наблюдателей.

Затем мы определяем скелет для создания новых наблюдателей. В метод update мы можем добавить необходимую функциональность нашего приложения.

Для большей наглядности мы создадим HTML разметку, которая будет содержать:

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

 

Затем мы определяем обработчики ConcreteSubject и ConcreteObserver для добавления новых наблюдателей на страницу и реализации интерфейса обновления. Ниже приведен код с инлайновыми комментариями.

HTML:

Наш скрипт:

В этом примере показано, как реализовать и использовать паттерн Observer, охватывающий концепции Subject, Observer, ConcreteSubject и ConcreteObserver.

Различия между паттернами Observer и Publish/Subscribe

Несмотря на то, что знать такой паттерн как Observer нужно каждому разработчику, в мире JavaScript мы можем обнаружить, что он обычно реализуется с использованием другой вариации, известной как паттерн Publish/Subscribe. Они очень похожи, но всё же между ними есть различия, которые стоит отметить.

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

Однако паттерн Publish/Subscribe использует topic/event channel (канал тема/событие) , который находится между объектами, желающими получать уведомления (подписчиками), и объектом, запускающим событие (издателем). Эта система событий позволяет программе определять специфичные события, которые могут передавать пользовательские аргументы, содержащие значения, необходимые подписчику. Идея здесь заключается в том, чтобы избежать прямой зависимости между подписчиком и издателем.

Вот пример того, как можно использовать Publish/Subscribe, при наличии необходимой функциональной реализации publish()subscribe()и unsubscribe():

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

Преимущества

Паттерны Observer и Publish/Subscribe заставляют нас задуматься о взаимоотношениях между различными частями нашего приложения. Они помогают нам определить, какие слои содержат прямые отношения, которые можно заменить на субъекты и наблюдатели. Это позволяет разбить приложение на мелкие куски, которые будут минимально связаны с другими частями нашегов кода, что улучшит управление и предоставит возможность повторно использовать код.

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

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

Несмотря на то, Observer и и Publish/Subscribe не всегда является идеальным решением, всё же эти шаблоны остаются одними из лучших инструментов для разработки развязанных систем и должны рассматриваться как важный инструмент в списке утилит разработчика JavaScript.

Недостатки

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

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

 


Это была первая часть достаточно большой главы книги Эдди. Во второй части мы более детально рассмотрим имплементацию Publish/Subscribe.