Весной стартует сезон найма, успей отхватить свой оффер!

Паттерн «Наблюдатель» (Observer)

Наблюдатель (Observer) — это поведенческий паттерн проектирования, который позволяет объектам подписываться на события, происходящие в другом объекте, и автоматически получать уведомления об изменениях его состояния. Объект, отправляющий сообщения, называется издателем (subject), а объекты, подписывающиеся на его уведомления, — наблюдателями (observers).

Идея паттерна:

«Наблюдатель» упрощает координацию и поддержание согласованности между несколькими объектами, позволяя одному объекту (Subject) уведомлять других (Observers) о своих изменениях без жёсткой привязки к их реализации.

Когда применять «Наблюдатель»?

1

1. Необходима обратная связь (callbacks)

Когда один объект должен сообщать другим о своих изменениях, но не должен знать детали их реализации (принцип низкой связанности).

2

2. Множественные зависимости

Когда есть несколько зависимых объектов (наблюдателей), которые должны реагировать на изменения в одном объекте (издателе).

3

3. Динамическое взаимодействие

Когда количество наблюдателей не фиксировано на этапе компиляции, и должно меняться «на лету».

Как работает «Наблюдатель»?

  1. Интерфейс издателя (Subject)
    Описывает методы для подписки (attach) и отписки (detach) наблюдателей, а также для уведомления (notify).

  2. Интерфейс наблюдателя (Observer)
    Содержит метод update(), который вызывается издателем.

  3. Конкретный издатель (Concrete Subject)
    Хранит состояние, которое могут отслеживать наблюдатели, и реализует методы подписки/отписки. При изменении состояния вызывает notify().

  4. Конкретный наблюдатель (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 можно почитать здесь.