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) — реализация наблюдателя, которая реагирует на уведомление (например, обновляет свой интерфейс или выполняет бизнес-логику).
Взаимодействие происходит следующим образом:
- Наблюдатели регистрируются у субъекта через
attach. - При изменении состояния субъекта вызывается
notify, который перебирает всех зарегистрированных наблюдателей и вызывает их методupdate. - Каждый наблюдатель может запросить у субъекта актуальные данные (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 →