Открыть сервис

Observer pattern

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

История

Шаблон Observer был впервые формально описан в 1994 году группой авторов (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес) в книге «Design Patterns: Elements of Reusable Object-Oriented Software» (рус. «Приёмы объектно-ориентированного проектирования. Паттерны проектирования»). Эта книга, известная как «банда четырёх» (GoF), закрепила шаблон как один из 23 классических паттернов. Однако идея подписки на события существовала и раньше: в графических интерфейсах (например, в Smalltalk-80, 1980-е годы) и в системах обработки сообщений (например, в Unix-сигналах). Впоследствии шаблон получил широкое распространение в событийно-ориентированном программировании, веб-разработке (модель MVC, фреймворки React, Vue), а также в распределённых системах (брокеры сообщений, очереди).

Структура и участники

Шаблон Observer включает несколько ключевых элементов:

  • Субъект (Subject) — объект, который хранит состояние и управляет списком наблюдателей. Он предоставляет методы для подписки (attach), отписки (detach) и уведомления (notify).
  • Наблюдатель (Observer) — интерфейс, который определяет метод обновления (update), вызываемый субъектом при изменении состояния.
  • Конкретный субъект (ConcreteSubject) — реализация субъекта, хранящая конкретное состояние и при изменении вызывающая notify.
  • Конкретный наблюдатель (ConcreteObserver) — реализация наблюдателя, которая реагирует на уведомление (например, обновляет свой интерфейс или выполняет бизнес-логику).

Взаимодействие происходит следующим образом:

  1. Наблюдатели регистрируются у субъекта через attach.
  2. При изменении состояния субъекта вызывается notify, который перебирает всех зарегистрированных наблюдателей и вызывает их метод update.
  3. Каждый наблюдатель может запросить у субъекта актуальные данные (pull-модель) или получить их в составе уведомления (push-модель).

Классификация

Шаблон Observer можно разделить на несколько разновидностей в зависимости от способа передачи данных:

  • Push-модель — субъект передаёт наблюдателям все необходимые данные в составе уведомления. Наблюдатели получают готовые значения, но могут быть перегружены ненужной информацией.
  • Pull-модель — субъект уведомляет только о факте изменения, а наблюдатели сами запрашивают нужные данные. Это даёт гибкость, но требует дополнительных вызовов.
  • Модель с брокером (Event Bus) — между субъектом и наблюдателями вводится посредник (шина событий), который управляет подписками и маршрутизацией. Это снижает связанность, но усложняет архитектуру.

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

Реализация

В объектно-ориентированных языках шаблон Observer реализуется через интерфейсы или абстрактные классы. Пример на псевдокоде:

```python class Subject: def __init__(self): self._observers = [] def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self): for observer in self._observers: observer.update(self)

class Observer: def update(self, subject): pass

class ConcreteSubject(Subject): def __init__(self): super().__init__() self._state = 0 @property def state(self): return self._state @state.setter def state(self, value): self._state = value self.notify()

class ConcreteObserver(Observer): def update(self, subject): print(f"Observer: состояние изменилось на {subject.state}") ```

В современных языках и фреймворках шаблон часто реализуется через встроенные механизмы: события в C#, сигналы в Qt, реактивные потоки в RxJava, хуки в React или подписки в Vue.

Применение

Шаблон Observer широко используется в следующих областях:

  • Графические интерфейсы — обработка событий мыши, клавиатуры, изменение данных модели. Например, в архитектуре MVC (Model-View-Controller) модель выступает субъектом, а представления — наблюдателями.
  • Системы реального времени — мониторинг датчиков, биржевых котировок, чатов. При изменении данных все подписчики мгновенно получают обновления.
  • Распределённые системы — брокеры сообщений (RabbitMQ, Apache Kafka) реализуют шаблон Observer на уровне приложений: издатели отправляют сообщения в топики, а подписчики получают их.
  • Игровые движки — обработка событий (например, смерть персонажа, сбор предмета) через систему подписок.
  • Веб-разработка — реактивные фреймворки (React, Angular, Vue) используют Observer для автоматического обновления DOM при изменении состояния.

Преимущества и недостатки

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

  • Слабая связанность между субъектом и наблюдателями. Субъект не знает о конкретных реализациях наблюдателей, что упрощает расширение.
  • Поддержка отношения «один ко многим» без изменения кода субъекта при добавлении новых наблюдателей.
  • Возможность динамической подписки и отписки во время выполнения.

Недостатки:

  • Возможны проблемы с производительностью при большом количестве наблюдателей, особенно при синхронных уведомлениях (каждый вызов может быть затратным).
  • Риск «цепных реакций» — когда обновление одного наблюдателя вызывает изменение субъекта, что приводит к бесконечному циклу.
  • Сложность отладки, так как порядок уведомлений не гарантирован (если не реализована приоритезация).
  • Утечки памяти, если наблюдатели не отписываются от субъекта (например, при уничтожении объекта).

Критика

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

Интересные факты

  • В языке Java шаблон Observer реализован в стандартной библиотеке через класс java.util.Observable и интерфейс java.util.Observer, но начиная с Java 9 они объявлены устаревшими (deprecated) из-за недостатков дизайна.
  • В языке C# шаблон Observer реализован через события (event) и делегаты, что является встроенным механизмом языка.
  • В архитектуре Model-View-ViewModel (MVVM) шаблон Observer используется для двустороннего связывания данных (data binding), где ViewModel выступает субъектом, а View — наблюдателем.
  • В распределённых системах шаблон Observer лежит в основе паттерна «издатель-подписчик» (pub-sub), который часто реализуется через брокеры сообщений, такие как Apache Kafka, RabbitMQ или Redis Pub/Sub.

Источники

  • Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» (Design Patterns: Elements of Reusable Object-Oriented Software), 1994.
  • Фаулер М. «Архитектура корпоративных программных приложений» (Patterns of Enterprise Application Architecture), 2002.
  • Официальная документация Oracle: Java Tutorials — Observer Pattern (устаревшая).
  • Документация Microsoft: C# Events and Delegates.
  • Martin Fowler. «Event Sourcing» (martinfowler.com), 2005.

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →