Шаблоны проектирования — Наблюдатель

Краткое руководство и суть паттерна.

Nikita Goncharuk
Clean Code

--

Паттерн Наблюдатель — это один из наиболее часто используемых паттернов проектирования. Точнее говоря, он настолько распространен, что даже стандартизирован во многих языках (библиотеках) программирования. Например, в Java (устарел в Java 9), интегрирован в Python (так же, как и pip), встречается в C++ (если используем библиотеку Boost). Кроме того, Наблюдатель широко используется в качестве кастомного решения для индустрии. Чтобы оптимально использовать данный паттерн, необходимо “копнуть поглубже” и понять его особенности.

Паттерн Наблюдатель относится к поведенческим паттернам проектирования. Специфика поведенческих паттернов проектирования состоит в создании коммуникаций между классами и/или объектами. [взято с книги Design Patterns]

С чем же “едят” паттерн Наблюдатель? Это не монитор, который ходит и транслирует аналоговое телевидение (как на картинке). Цель паттерна — построить отношения со многими объектами таким образом, чтобы изменение состояния одного из них автоматически стало известным остальным и чтобы они также автоматически обновлялись. Точнее говоря, этот паттерн хочет получать информацию обо всех событиях, происходящих в системе. Следующие три шага раскроют нам полную картину.

Шаг 1 — Ключевые Слова

Ключевые слова — это секретный рецепт данной серии кратких руководств. Этот метод по-настоящему помог мне понять паттерны проектирования, прочно закрепил их в моей голове и показал их различия.

Объект: Считается хранителем информации, баз данных либо бизнес-логики.

Регистрация/Добавление: Наблюдатели самостоятельно регистрируются в субъекте, так как хотят получать уведомления обо всех изменениях.

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

Уведомление: В зависимости от реализации, либо объект может “кидать” информацию наблюдателям, либо, если информация нужна наблюдателям, они могут “вытянуть” её из объекта.

Обновление: Наблюдатели обновляют свое состояние независимо друг от друга, но их состояние может изменяться в связи с инициированным событием.

Шаг 2 — Схема

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

· ConcreteObservers — это классы, содержащие информацию, специфичную для текущего экземпляра. Функция обновления вызывается операцией notify() объекта. Наблюдатели обновляются независимо, с учетом их текущего состояния.

· Observer — это родительский класс конкретных наблюдателей. Он содержит экземпляр объекта. Когда наблюдатель инициализируется, он добавляется к объекту.

  • Класс Subject содержит список или коллекцию наблюдателей. Инициируемое событие вызывает действие, запуская функцию notify() обновления для всех наблюдателей.

Шаг 3 — Пример кода

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

  • Игра — это объект, а болельщики — наблюдатели.

· Все наблюдатели добавлены к объекту и они информируются, когда их футбольная команда забивает (триггер-событие, если команда увеличивает счет).

· Наблюдатели обновляют свое поведение в соответствии с полученной информацией.

Subject
Для этого класса нам нужен доступ к списку наблюдателей. Когда наблюдатели готовы регистрироваться, они вызывают функцию attach(this), чтобы добавить самих себя в доступный список (this это экземпляр наблюдателя). Когда событие запускается, все наблюдатели независимо обновляют свое состояние после вызова notify(). В нашем примере триггер срабатывает, если футбольная команда наблюдателя забила гол.

Observer
Этот класс зависим от объекта, в котором он зарегистрирован. Когда конкретные наблюдатели инициализируются, они присоединяются к Subject. В этом примере состоянием каждого наблюдателя выступает его excitementLevel по отношению к игре.

Это Subject::notify() декларирование, и как мы уже упоминали ранее, его задача состоит в том, чтобы проинформировать всех наблюдателей об обновлении их состояния.

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

Функция Main
Конкретные наблюдатели регистрируются в экземпляре Subject самостоятельно. Их состояние отражает степень возбуждения, являющуюся вторым параметром. Когда событие инициируется — subj.setScored(true)0 — вызывается обновление Subject::notify() зарегистрированных наблюдателей. В приведенном ниже сценарии у нас есть три Наблюдателя. Первый — молодой болельщик youngObs1, может сесть за руль после употребления алкоголя. Второй — пожилой oldObs2, подвержен риску возникновения сердечного приступа. Наконец, третий — youngObs3, который так же молод, как и первый, может ни о чем не беспокоится, поскольку он не перевозбужден.

Важно отметить, что все три наблюдателя обновляются независимо друг от друга, но в зависимости от их состояния (уровень возбуждения) и возраста (молодой или пожилой).

Использование паттерна Наблюдатель дает несколько преимуществ, если понять несколько моментов, когда нужно использовать этот паттерн. [Изучение Python Design Patterns].

· Паттерн Наблюдатель предоставляет конструкцию со слабой связью между Объектом и Наблюдателем. Субъекту не нужно знать о классе ConcreteObserver. Новый Наблюдатель может быть добавлен в любое время. Нет необходимости изменять Объект при добавлении нового Наблюдателя. Наблюдатели и объекты не связаны и не зависят друг от друга, поэтому изменения в Объекте или Наблюдателе не влияют друг на друга.

◆ Возможность использовать композицию не предусмотрена, поскольку может создаваться интерфейс Наблюдателя.

◆ Если Наблюдатель используется неправильно, это может усложнить код и привести к проблемам с производительностью.

◆ Уведомления не всегда надежны, что несет риск несогласованности действий.

Спасибо за внимание!

--

--