Наблюдатель

Описание
Наблюдатель — поведенческий шаблон проектирования, также известен как «подчинённые» (Dependents). Создает механизм у класса, который позволяет получать экземпляру объекта этого класса оповещения от других объектов об изменении их состояния, тем самым наблюдая за ними.

Шаблон определяет зависимость между объектами, чтобы при изменении состояния одного из них зависимые от него узнавали об этом.

Когда использовать паттерн
  • Когда изменения в состоянии одного объекта должны приводить к изменениям в других объектах.

  • Когда один объект должен оповещать множество других объектов о своём состоянии.

  • Когда нужно минимизировать зависимости между объектами, чтобы улучшить гибкость и расширяемость кода.
Основные участники паттерна
Subject (Наблюдаемый) — интерфейс или класс, который поддерживает список наблюдателей и оповещает их об изменениях.

Observer (Наблюдатель) — интерфейс или класс, который подписывается на уведомления от наблюдаемого объекта.

ConcreteSubject (Конкретный наблюдаемый) — класс, который реализует интерфейс Subject и управляет состоянием.

ConcreteObserver (Конкретный наблюдатель) — класс, реализующий интерфейс Observer и выполняющий действия при получении уведомлений.
Пример реализации паттерна
Предположим, у нас есть приложение погоды. Система отслеживает температуру и оповещает зарегистрированных пользователей (наблюдателей) об изменениях. Каждый пользователь может получать уведомления о смене температуры.
Шаг 1: Определим интерфейсы наблюдаемого и наблюдателя
from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, temperature):
        pass

class Subject(ABC):
    @abstractmethod
    def attach(self, observer: Observer):
        pass
    
    @abstractmethod
    def detach(self, observer: Observer):
        pass
    
    @abstractmethod
    def notify(self):
        pass
Observer — интерфейс для наблюдателей, определяющий метод update, который будет вызываться при получении обновлений.

Subject — интерфейс для наблюдаемых объектов, с методами attach для подписки наблюдателя, detach для отписки и notify для оповещения всех наблюдателей об изменениях.
Шаг 2: Создадим конкретные классы для наблюдаемого объекта
class WeatherStation(Subject):
    def __init__(self):
        self._observers = []
        self._temperature = 0

    def attach(self, observer: Observer):
        self._observers.append(observer)

    def detach(self, observer: Observer):
        self._observers.remove(observer)

    def notify(self):
        for observer in self._observers:
            observer.update(self._temperature)

    def set_temperature(self, temperature):
        print(f"WeatherStation: Setting temperature to {temperature}")
        self._temperature = temperature
        self.notify()  # Оповещаем всех наблюдателей
WeatherStation хранит список наблюдателей (_observers) и реализует методы attach, detach и notify.

Метод set_temperature изменяет температуру и оповещает всех наблюдателей об изменении.
Шаг 3: Реализация конкретных наблюдателей
class PhoneDisplay(Observer):
    def update(self, temperature):
        print(f"PhoneDisplay: The temperature is now {temperature}°C")

class WindowDisplay(Observer):
    def update(self, temperature):
        print(f"WindowDisplay: The temperature is now {temperature}°C")
PhoneDisplay и WindowDisplay — это конкретные наблюдатели, каждый из которых реализует метод update. Они получают уведомления об изменении температуры и выводят новые данные на экран.
Шаг 4: Использование паттерна
def main():
    # Создаем наблюдаемый объект
    weather_station = WeatherStation()
    
    # Создаем наблюдателей
    phone_display = PhoneDisplay()
    window_display = WindowDisplay()
    
    # Подписываем наблюдателей на обновления
    weather_station.attach(phone_display)
    weather_station.attach(window_display)
    
    # Изменяем температуру, чтобы оповестить наблюдателей
    weather_station.set_temperature(25)
    weather_station.set_temperature(30)
    
    # Отключаем одного наблюдателя и снова изменяем температуру
    weather_station.detach(window_display)
    weather_station.set_temperature(20)

main()
Результат выполнения
WeatherStation: Setting temperature to 25
PhoneDisplay: The temperature is now 25°C
WindowDisplay: The temperature is now 25°C
WeatherStation: Setting temperature to 30
PhoneDisplay: The temperature is now 30°C
WindowDisplay: The temperature is now 30°C
WeatherStation: Setting temperature to 20
PhoneDisplay: The temperature is now 20°C
Как работает код?
  • WeatherStation управляет списком подписанных наблюдателей и уведомляет их при изменении температуры.
  • PhoneDisplay и WindowDisplay реагируют на изменение температуры, отображая её.
  • Когда наблюдатель отписывается, он больше не получает уведомлений о смене состояния.
Преимущества паттерна
  • Слабая связанность. Класс, который уведомляет, не знает, кто именно его наблюдает, что повышает гибкость.

  • Масштабируемость. Легко добавить новых наблюдателей, не изменяя код наблюдаемого объекта.

  • Автоматизация. Все наблюдатели автоматически получают обновления, не требуя постоянного запроса.
Недостатки
  • Потенциальное замедление. Многочисленные уведомления могут замедлять работу приложения, если подписчиков слишком много.

  • Сложность отладки. Из-за слабой связанности бывает трудно отследить, кто именно изменяет состояние.