Шаблоны проектирования — Наблюдатель
Краткое руководство и суть паттерна.
Паттерн Наблюдатель — это один из наиболее часто используемых паттернов проектирования. Точнее говоря, он настолько распространен, что даже стандартизирован во многих языках (библиотеках) программирования. Например, в 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. Новый Наблюдатель может быть добавлен в любое время. Нет необходимости изменять Объект при добавлении нового Наблюдателя. Наблюдатели и объекты не связаны и не зависят друг от друга, поэтому изменения в Объекте или Наблюдателе не влияют друг на друга.
◆ Возможность использовать композицию не предусмотрена, поскольку может создаваться интерфейс Наблюдателя.
◆ Если Наблюдатель используется неправильно, это может усложнить код и привести к проблемам с производительностью.
◆ Уведомления не всегда надежны, что несет риск несогласованности действий.
Спасибо за внимание!