Паттерн «Наблюдатель» (Observer)
Наблюдатель (Observer) — это поведенческий паттерн проектирования, который позволяет объектам подписываться на события, происходящие в другом объекте, и автоматически получать уведомления об изменениях его состояния. Объект, отправляющий сообщения, называется издателем (subject), а объекты, подписывающиеся на его уведомления, — наблюдателями (observers).
Идея паттерна:
«Наблюдатель» упрощает координацию и поддержание согласованности между несколькими объектами, позволяя одному объекту (Subject) уведомлять других (Observers) о своих изменениях без жёсткой привязки к их реализации.
Когда применять «Наблюдатель»?
1. Необходима обратная связь (callbacks)
Когда один объект должен сообщать другим о своих изменениях, но не должен знать детали их реализации (принцип низкой связанности).
2. Множественные зависимости
Когда есть несколько зависимых объектов (наблюдателей), которые должны реагировать на изменения в одном объекте (издателе).
3. Динамическое взаимодействие
Когда количество наблюдателей не фиксировано на этапе компиляции, и должно меняться «на лету».
Как работает «Наблюдатель»?
-
Интерфейс издателя (Subject)
Описывает методы для подписки (attach) и отписки (detach) наблюдателей, а также для уведомления (notify). -
Интерфейс наблюдателя (Observer)
Содержит методupdate()
, который вызывается издателем. -
Конкретный издатель (Concrete Subject)
Хранит состояние, которое могут отслеживать наблюдатели, и реализует методы подписки/отписки. При изменении состояния вызываетnotify()
. -
Конкретный наблюдатель (Concrete Observer)
Реагирует на уведомления, получая нужные данные от издателя.
Пример (TypeScript)
// 1. Интерфейс наблюдателя
interface Observer {
update(subject: Subject): void;
}
// 2. Интерфейс издателя
interface Subject {
attach(observer: Observer): void;
detach(observer: Observer): void;
notify(): void;
}
// 3. Конкретный издатель
class ConcreteSubject implements Subject {
private observers: Observer[] = [];
private state: number = 0;
public attach(observer: Observer): void {
this.observers.push(observer);
}
public detach(observer: Observer): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
public notify(): void {
for (const observer of this.observers) {
observer.update(this);
}
}
// Пример изменения состояния
public setState(value: number) {
this.state = value;
console.log(`Subject: состояние изменилось на ${this.state}`);
this.notify();
}
public getState(): number {
return this.state;
}
}
// 4. Конкретный наблюдатель
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.getState() < 5) {
console.log("ConcreteObserverA реагирует, так как состояние < 5");
}
}
}
class ConcreteObserverB implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.getState() >= 5) {
console.log("ConcreteObserverB реагирует, так как состояние >= 5");
}
}
}
// Пример использования
function clientCode() {
const subject = new ConcreteSubject();
const observerA = new ConcreteObserverA();
const observerB = new ConcreteObserverB();
subject.attach(observerA);
subject.attach(observerB);
// Изменяем состояние
subject.setState(3);
subject.setState(7);
subject.detach(observerA);
subject.setState(2); // Теперь только observerB отслеживает
}
clientCode();
- ConcreteSubject хранит состояние (state) и уведомляет всех подписчиков при его изменении.
- ConcreteObserverA и ConcreteObserverB по-своему реагируют на изменения, опрашивая subject.
Преимущества
- Слабая связность: Издатель не знает деталей наблюдателей, только вызывает их метод update().
- Гибкость: Можно динамически добавлять, удалять и переключать наблюдателей.
- Легко расширять: Новые типы наблюдателей добавляются без изменений издателя.
Недостатки
- Каскад уведомлений: Ошибка или избыточное уведомление может привести к «цепной реакции» и усложнить отладку.
- Порядок уведомлений: Если у разных наблюдателей есть зависимости друг от друга, порядок вызова update() может иметь значение.
Важно:
Паттерн «Наблюдатель» может быть потенциально опасен при большом количестве подписчиков или сложных зависимостях. Тщательно отслеживайте, какие события посылаются и куда, чтобы не превратить проект в «спагетти»-код.
Когда использовать
- Когда необходимо централизованно обрабатывать события и оповещать несколько заинтересованных объектов.
- Когда нужно обновлять объекты при изменении одного источника без прямой зависимости (например, система подписки/уведомлений).
- Когда важно, чтобы новые модули могли подписаться на события без изменения уже существующего кода.
Источник
Паттерн «Наблюдатель» упрощает реализацию реактивных приложений и снижает связанность между компонентами. Он широко используется во фронтенде (например, в архитектуре Flux/Redux, где подписчики реагируют на изменение состояния магазина). Однако будьте осторожны с потенциальным ростом сложности при большом числе подписчиков и пересекающихся событий.
Подробнее про Observer можно почитать здесь.