Паттерн Наблюдатель в Python
Паттерн Наблюдатель — это поведенческий паттерн проектирования, который определяет зависимость «один ко многим», позволяя нескольким объектам-наблюдателям одновременно следить за определённым объектом-субъектом. Когда состояние объекта-субъекта изменяется, все его наблюдатели получают уведомление и автоматически обновляются.
Паттерн Наблюдатель в повседневной жизни
Представьте сценарий подписки на газету:
- Редакция газеты(Субъект) публикует новости
- Подписчики(Наблюдатель) подписываются на газету
- Когда публикуется новая газета, все подписчики автоматически получают газету
- Подписчики могут отменить свою подписку в любое время
Этот механизм «публикация-подписка» — типичный пример применения паттерна «Наблюдатель».
Основные компоненты паттерна «Наблюдатель»
Паттерн «Наблюдатель» содержит четыре ключевые роли:
1. Субъект
- Поддерживает список наблюдателей
- Предоставляет методы для регистрации и отмены регистрации наблюдателей
- Уведомляет всех наблюдателей об изменениях состояния
2. Конкретный субъект
- Хранит конкретную информацию о состоянии
- Уведомляет наблюдателей при изменении состояния
3. Наблюдатель
- Определяет интерфейс обновления для получения уведомлений от субъекта
4. Конкретный наблюдатель
- Реализует интерфейс обновления, чтобы оставаться согласованным с состоянием субъекта
Реализация паттерна «Наблюдатель»
Давайте разберём реализацию паттерна «Наблюдатель» на полном примере.
Базовая реализация
Пример
from typing import List
# Интерфейс Observer
class Observer(ABC):
@abstractmethod
def update(self, message: str):
"""Метод обновления Observer"""
pass
# Интерфейс Subject
class Subject(ABC):
@abstractmethod
def register_observer(self, observer: Observer):
"""Зарегистрировать наблюдателя"""
pass
@abstractmethod
def remove_observer(self, observer: Observer):
"""Удалить наблюдателя"""
pass
@abstractmethod
def notify_observers(self):
"""Уведомить всех наблюдателей"""
pass
# Конкретный субъект - издатель новостей
class NewsPublisher(Subject):
def __init__(self):
self._observers: List[Observer] = []
self._latest_news = ""
def register_observer(self, observer: Observer):
"""Зарегистрировать наблюдателя"""
if observer not in self_observers:
self._observers.append(observer)
print(f"Наблюдатель {observer.__class__.__name__} успешно зарегистрирован")
def remove_observer(self, observer: Observer):
"""Удалить наблюдателя"""
if observer in self._observers:
self._observers.remove(observer)
print(f"Наблюдатель {observer.__class__.__name__} успешно удалён")
def notify_observers(self):
"""Уведомить всех наблюдателей"""
print(f"\nПубликация нового сообщения: {self._latest_news}")
for observer in self._observers:
observer.update(self._latest_news)
def publish_news(self, news: str):
"""Публикует новости"""
self._latest_news = news
self.notify_observers()
def get_observer_count(self) -> int:
"""Возвращает количество наблюдателей"""
return len(self._observers)
# Конкретный наблюдатель - подписчик электронной почты
class EmailSubscriber(Observer):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
"""Получить обновление и отправить электронное письмо"""
print(f"[Email] {self.name} получил сообщение: {message}")
# Конкретный наблюдатель - SMS-подписчик
class SMSSubscriber(Observer):
def __init__(self, name: str, phone: str):
self.name = name
self.phone = phone
def update(self, message: str):
"""Получить обновление и отправить SMS"""
print(f"[SMS] {self.phone} - {self.name} получил сообщение: {message}")
# Конкретный наблюдатель - подписчик push-уведомлений приложения
class AppPushSubscriber(Observer):
def __init__(self, username: str):
self.username = username
def update(self, message: str):
"""Получить обновление и отправить push-уведомление в приложение"""
print(f"[Push приложения] Пользователь {self.username} получил push: {message}")
Пример использования
Пример
"""Демонстрация базового паттерна Observer"""
print("=== Демонстрация базового паттерна Observer ==="\n")
# Создать издателя новостей
publisher = NewsPublisher()
# Создать разные типы подписчиков
email_user = EmailSubscriber("Чжан Сань")
sms_user = SMSSubscriber("Ли Сы", "13800138000")
app_user = AppPushSubscriber("Ван У")
# Зарегистрировать подписчиков
publisher.register_observer(email_user)
publisher.register_observer(sms_user)
publisher.register_observer(app_user)
print(f"\nТекущее количество подписчиков: {publisher.get_observer_count()}")
# Опубликовать новости
publisher.publish_news("Python 3.12 официально выпущен!")
# Удалить подписчика
publisher.remove_observer(sms_user)
print(f"\nКоличество подписчиков после удаления: {publisher.get_observer_count()}")
# Снова опубликовать новость
publisher.publish_news("Руководство по изучению паттерна Observer теперь онлайн!")
# Запустить демонстрацию
if __name__ == "__main__":
demo_basic_observer()
Вывод:
=== 观察者模式基础演示 === 观察者 EmailSubscriber 注册成功 观察者 SMSSubscriber 注册成功 观察者 AppPushSubscriber 注册成功 当前订阅者数量: 3 发布新消息: Python 3.12 正式发布! [邮件] 张三 收到消息: Python 3.12 正式发布! [短信] 13800138000 - 李四 收到消息: Python 3.12 正式发布! [应用推送] 用户 王五 收到推送: Python 3.12 正式发布! 观察者 SMSSubscriber 移除成功 移除后订阅者数量: 2 发布新消息: 观察者模式学习指南上线! [邮件] 张三 收到消息: 观察者模式学习指南上线! [应用推送] 用户 王五 收到推送: 观察者模式学习指南上线!
Продвинутое применение: система мониторинга цен на акции
Давайте углубим наше понимание на более практичном примере.
Пример
class Stock:
"""Класс Stock"""
def __init__(self, symbol: str, price: float):
self.symbol = symbol # Код акции
self._price = price # Текущая цена
self._history = [price] # История цен
@property
def price(self) -> float:
return self._price
@price.setter
def price(self, value: float):
"""Установить цену и записать историю"""
old_price = self._price
self._price = value
self._history.append(value)
return old_price, value
def get_price_change(self) -> float:
"""Получить процент изменения цены"""
if len(self._history) < 2:
return 0.0
return ((self._history[-1] - self._history[-2]) / self._history[-2]) * 100
class StockMarket(Subject):
"""Фондовый рынок - конкретный субъект"""
def __init__(self):
self._observers: List[Observer] = []
self._stocks = {} # Словарь акций: {code: Stock object}
def register_observer(self, observer: Observer):
if observer not in self_observers:
self._observers.append(observer)
def remove_observer(self, observer: Observer):
if observer in self._observers:
self._observers.remove(observer)
def notify_observers(self, stock: Stock):
"""Уведомить наблюдателей об изменении конкретной акции"""
change = stock.get_price_change()
message = f"{stock.symbol}: ${stock.price:.2f} ({change:+.2f}%)"
for observer in self._observers:
observer.update(message)
def add_stock(self, symbol: str, initial_price: float):
"""Добавить акцию на рынок"""
self._stocks[symbol] = Stock(symbol, initial_price)
def update_stock_price(self, symbol: str, new_price: float):
"""Обновить цену акции и уведомить наблюдателей"""
if symbol in self._stocks:
stock = self._stocks[symbol]
old_price, current_price = stock.price, new_price
stock.price = new_price
# Уведомлять только при фактическом изменении цены
if old_price != current_price:
self.notify_observers(stock)
class Investor(Observer):
"""Инвестор - Конкретный наблюдатель"""
def __init__(self, name: str, interest_stocks: List[str] = None):
self.name = name
self.interest_stocks = interest_stocks or []
self.notifications = []
def update(self, message: str):
"""Получать обновления акций"""
stock_symbol = message.split(":")[0]
# Обращать внимание только на интересующие акции
if not self.interest_stocks or stock_symbol in self.interest_stocks:
self.notifications.append(message)
print(f"Инвестор {self.name} получил: {message}")
def show_notifications(self):
"""Показать все уведомления"""
print(f"\n=== записи уведомлений {self.name} ===")
for notification in self.notifications:
print(f" - {notification}")
def demo_stock_market():
"""Демонстрация фондового рынка"""
print("\n=== Демонстрация системы мониторинга фондового рынка ===\n")
# Создать фондовый рынок
market = StockMarket()
# Добавить акции
market.add_stock("AAPL", 150.0)
market.add_stock("GOOGL", 2800.0)
market.add_stock("TSLA", 250.0)
# Создать инвесторов
investor1 = Investor("Технологический инвестор", ["AAPL", "GOOGL"])
investor2 = Investor("Инвестор в новую энергетику", ["TSLA"])
investor3 = Investor("Инвестор всего рынка")
# Регистрация инвесторов
market.register_observer(investor1)
market.register_observer(investor2)
market.register_observer(investor3)
# Симуляция изменений цен акций
print("Начинаем симуляцию торговли акциями...")
market.update_stock_price("AAPL", 152.5) # AAPL растёт
market.update_stock_price("TSLA", 245.0) # TSLA падает
market.update_stock_price("GOOGL", 2820.0) # GOOGL растёт
market.update_stock_price("AAPL", 151.0) # AAPL слегка падает
# Отобразить записи уведомлений инвестора
investor1.show_notifications()
investor2.show_notifications()
investor3.show_notifications()
# Запустить демонстрацию фондового рынка
demo_stock_market()
Преимущества и недостатки паттерна «Наблюдатель»
Преимущества
| Преимущества | Описание |
|---|---|
| Слабая связанность | Зависимость между субъектом и наблюдателями слабая, что позволяет им изменяться независимо |
| Расширяемость | Новых наблюдателей можно легко добавлять, не изменяя код субъекта |
| Широковещательная связь | Поддерживает коммуникацию «один ко многим»; сообщения могут широковещательно рассылаться нескольким объектам |
| Принцип открытости/закрытости | Открыт для расширения, закрыт для изменения |
Недостатки
| Недостатки | Описание | Решение |
|---|---|---|
| Утечка памяти | Если наблюдатели забывают отменять регистрацию, это может привести к утечкам памяти | Используйте слабые ссылки или обеспечьте своевременную отмену регистрации |
| Порядок обновления | Порядок обновления наблюдателей не определён | Если порядок необходим, можно добавить механизм приоритетов |
| Проблемы с производительностью | Когда наблюдателей много, уведомления могут влиять на производительность | Используйте асинхронные уведомления или пакетные обновления |
Практические сценарии применения
1. Обработка событий GUI
Пример
button.on_click(handler1) # Регистрация наблюдателя события click
button.on_click(handler2) # Зарегистрировать другого наблюдателя
# При нажатии кнопки будут вызваны все зарегистрированные обработчики
2. Система очередей сообщений
- Производитель публикует сообщение
- Несколько потребителей подписываются и обрабатывают сообщения
3. Система мониторинга данных
- Изменение состояния источника данных
- Несколько сервисов мониторинга получают уведомления и предпринимают действия
4. Разработка игр
- Система игровых событий
- Элементы интерфейса реагируют на изменения состояния игры
Лучшие практики и рекомендации
1. Использование слабых ссылок для предотвращения утечек памяти
Пример
class SafeSubject(Subject):
def __init__(self):
self._observers = weakref.WeakSet() # Использовать коллекцию слабых ссылок
def register_observer(self, observer: Observer):
self._observers.add(observer)
2. Асинхронное уведомление для повышения производительности
Пример
class AsyncObserver(ABC):
@abstractmethod
async def update_async(self, message: str):
pass
class AsyncSubject:
async def notify_observers_async(self):
tasks = [observer.update_async(self._message)
for observer in self._observers]
await asyncio.gather(*tasks)
3. Добавление обработки ошибок
Пример
for observer in self._observers.copy(): # Используйте копию, чтобы избежать проблем с изменением
try:
observer.update(self._message)
except Exception as e:
print(f"Не удалось обновить наблюдателя {observer}: {e}")
Итог
Паттерн «Наблюдатель» — очень важный паттерн проектирования в Python. Он предоставляет элегантный способ достижения слабой связанности между объектами. После прочтения этой статьи вы должны уметь:
- Понимать основные концепции и сценарии применения паттерна «Наблюдатель»
- Освоить базовые методы реализации паттерна «Наблюдатель»
- Применять паттерн «Наблюдатель» для решения задач в реальных проектах
- Избегать распространённых ошибок паттерна «Наблюдатель»