Patrón Observador en Python
El patrón observador es un patrón de diseño de comportamiento que define una relación de dependencia de uno a muchos, permitiendo que múltiples objetos observadores escuchen simultáneamente a un objeto sujeto. Cuando el estado del objeto sujeto cambia, todos sus observadores reciben una notificación y se actualizan automáticamente.
El patrón observador en la vida cotidiana
Imagina el escenario de la suscripción a un periódico:
- Editorial del periódico(Sujeto) Publica noticias
- Suscriptores(Observadores) Se suscriben al periódico
- Cuando se publica un nuevo periódico, todos los suscriptores lo reciben automáticamente
- Los suscriptores pueden cancelar la suscripción en cualquier momento
Este mecanismo de "publicar-suscribir" es una aplicación típica del patrón observador.
Componentes centrales del patrón observador
El patrón observador incluye cuatro roles clave:
1. Sujeto (Subject)
- Mantiene una lista de observadores
- Proporciona métodos para registrar y cancelar el registro de observadores
- Notifica a todos los observadores sobre cambios de estado
2. Sujeto concreto (Concrete Subject)
- Almacena información de estado específica
- Notifica a los observadores cuando cambia el estado
3. Observador (Observer)
- Define una interfaz de actualización para recibir notificaciones del sujeto
4. Observador concreto (Concrete Observer)
- Implementa la interfaz de actualización para mantenerse sincronizado con el estado del sujeto
Implementación del patrón observador
Entendamos la implementación del patrón observador mediante un ejemplo completo.
Implementación básica
Ejemplo
from typing import List
# Interfaz del observador
class Observer(ABC):
@abstractmethod
def update(self, message: str):
"""Método de actualización del observador"""
pass
# Interfaz del sujeto
class Subject(ABC):
@abstractmethod
def register_observer(self, observer: Observer):
"""Registrar observador"""
pass
@abstractmethod
def remove_observer(self, observer: Observer):
"""Eliminar observador"""
pass
@abstractmethod
def notify_observers(self):
"""Notificar a todos los observadores"""
pass
# Sujeto concreto - Publicador de noticias
class NewsPublisher(Subject):
def __init__(self):
self._observers: List[Observer] = []
self._latest_news = ""
def register_observer(self, observer: Observer):
"""Registrar observador"""
if observer not in self._observers:
self._observers.append(observer)
print(f"Observador {observer.__class__.__name__} registrado exitosamente")
def remove_observer(self, observer: Observer):
"""Eliminar observador"""
if observer in self._observers:
self._observers.remove(observer)
print(f"Observador {observer.__class__.__name__} eliminado exitosamente")
def notify_observers(self):
"""Notificar a todos los observadores"""
print(f"\nPublicar nuevo mensaje: {self._latest_news}")
for observer in self._observers:
observer.update(self._latest_news)
def publish_news(self, news: str):
"""Publicar noticias"""
self._latest_news = news
self.notify_observers()
def get_observer_count(self) -> int:
"""Obtener número de observadores"""
return len(self._observers)
# Observador concreto - Suscriptor de correo electrónico
class EmailSubscriber(Observer):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
"""Recibir actualización y enviar correo electrónico"""
print(f"[Correo] {self.name} recibió el mensaje: {message}")
# Observador concreto - Suscriptor de SMS
class SMSSubscriber(Observer):
def __init__(self, name: str, phone: str):
self.name = name
self.phone = phone
def update(self, message: str):
"""Recibir actualización y enviar SMS"""
print(f"[SMS] {self.phone} - {self.name} recibió el mensaje: {message}")
# Observador concreto - Suscriptor de notificaciones push de la aplicación
class AppPushSubscriber(Observer):
def __init__(self, username: str):
self.username = username
def update(self, message: str):
"""Recibir actualización y enviar notificación push de la aplicación"""
print(f"[Push de la aplicación] El usuario {self.username} recibió la notificación: {message}")
Ejemplo de uso
Ejemplo
"""Demostración básica del patrón observador"""
print("=== Demostración básica del patrón observador ===\n")
# Crear publicador de noticias
publisher = NewsPublisher()
# Crear diferentes tipos de suscriptores
email_user = EmailSubscriber("Zhang San")
sms_user = SMSSubscriber("Li Si", "13800138000")
app_user = AppPushSubscriber("Wang Wu")
# Registrar suscriptores
publisher.register_observer(email_user)
publisher.register_observer(sms_user)
publisher.register_observer(app_user)
print(f"\nNúmero actual de suscriptores: {publisher.get_observer_count()}")
# Publicar noticias
publisher.publish_news("¡Python 3.12 publicado oficialmente!")
# Eliminar un suscriptor
publisher.remove_observer(sms_user)
print(f"\nNúmero de suscriptores después de eliminar: {publisher.get_observer_count()}")
# Publicar noticias nuevamente
publisher.publish_news("¡Guía de aprendizaje del patrón observador disponible!")
# Ejecutar demostración
if __name__ == "__main__":
demo_basic_observer()
Resultado de la ejecución:
=== 观察者模式基础演示 === 观察者 EmailSubscriber 注册成功 观察者 SMSSubscriber 注册成功 观察者 AppPushSubscriber 注册成功 当前订阅者数量: 3 发布新消息: Python 3.12 正式发布! [邮件] 张三 收到消息: Python 3.12 正式发布! [短信] 13800138000 - 李四 收到消息: Python 3.12 正式发布! [应用推送] 用户 王五 收到推送: Python 3.12 正式发布! 观察者 SMSSubscriber 移除成功 移除后订阅者数量: 2 发布新消息: 观察者模式学习指南上线! [邮件] 张三 收到消息: 观察者模式学习指南上线! [应用推送] 用户 王五 收到推送: 观察者模式学习指南上线!
Aplicación avanzada: Sistema de monitoreo de precios de acciones
Profundicemos nuestra comprensión con un ejemplo más práctico.
Ejemplo
class Stock:
"""Clase de acción (stock)"""
def __init__(self, symbol: str, price: float):
self.symbol = symbol # Código de la acción
self._price = price # Precio actual
self._history = [price] # Historial de precios
@property
def price(self) -> float:
return self._price
@price.setter
def price(self, value: float):
"""Establecer precio y registrar historial"""
old_price = self._price
self._price = value
self._history.append(value)
return old_price, value
def get_price_change(self) -> float:
"""Obtener porcentaje de cambio de precio"""
if len(self._history) < 2:
return 0.0
return ((self._history[-1] - self._history[-2]) / self._history[-2]) * 100
class StockMarket(Subject):
"""Mercado de acciones - Sujeto concreto"""
def __init__(self):
self._observers: List[Observer] = []
self._stocks = {} # Diccionario de acciones: {código: objeto Stock}
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):
"""Notificar a los observadores cambios en una acción específica"""
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):
"""Agregar acción al mercado"""
self._stocks[symbol] = Stock(symbol, initial_price)
def update_stock_price(self, symbol: str, new_price: float):
"""Actualizar el precio de la acción y notificar a los observadores"""
if symbol in self._stocks:
stock = self._stocks[symbol]
old_price, current_price = stock.price, new_price
stock.price = new_price
# Solo notificar cuando el precio realmente cambia
if old_price != current_price:
self.notify_observers(stock)
class Investor(Observer):
"""Inversor - Observador concreto"""
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):
"""Recibir actualización de acciones"""
stock_symbol = message.split(":")[0]
# Solo prestar atención a las acciones de interés
if not self.interest_stocks or stock_symbol in self.interest_stocks:
self.notifications.append(message)
print(f"Inversor {self.name} recibió: {message}")
def show_notifications(self):
"""Mostrar todas las notificaciones"""
print(f"\n=== Registro de notificaciones de {self.name} ===)
for notification in self.notifications:
print(f" - {notification}")
def demo_stock_market():
"""Demostración del mercado de acciones"""
print("\n=== Demostración del sistema de monitoreo del mercado de acciones ===\n")
# Crear mercado de acciones
market = StockMarket()
# Agregar acciones
market.add_stock("AAPL", 150.0)
market.add_stock("GOOGL", 2800.0)
market.add_stock("TSLA", 250.0)
# Crear inversores
investor1 = Investor("Inversor tecnológico", ["AAPL", "GOOGL"])
investor2 = Investor("Inversor de nuevas energías", ["TSLA"])
investor3 = Investor("Inversor de todo el mercado")
# Registrar inversores
market.register_observer(investor1)
market.register_observer(investor2)
market.register_observer(investor3)
# Simular cambios en los precios de las acciones
print("Comenzando la simulación de operaciones bursátiles...")
market.update_stock_price("AAPL", 152.5) # AAPL sube
market.update_stock_price("TSLA", 245.0) # TSLA baja
market.update_stock_price("GOOGL", 2820.0) # GOOGL sube
market.update_stock_price("AAPL", 151.0) # AAPL baja ligeramente
# Mostrar registros de notificaciones de inversores
investor1.show_notifications()
investor2.show_notifications()
investor3.show_notifications()
# Ejecutar demostración del mercado de acciones
demo_stock_market()
Ventajas y desventajas del patrón observador
Ventajas
| Ventaja | Descripción |
|---|---|
| Acoplamiento débil | La dependencia entre el sujeto y los observadores es débil, pueden cambiar de forma independiente |
| Extensibilidad | Se pueden agregar fácilmente nuevos observadores sin modificar el código del sujeto |
| Comunicación por difusión | Admite comunicación de uno a muchos; los mensajes se pueden difundir a múltiples objetos |
| Principio abierto-cerrado | Abierto a la extensión, cerrado a la modificación |
Desventajas
| Desventaja | Descripción | Solución |
|---|---|---|
| Fuga de memoria | Si el observador olvida cancelar su registro, puede causar fugas de memoria | Usar referencias débiles o asegurarse de cancelar el registro oportunamente |
| Orden de actualización | El orden de actualización de los observadores es indeterminado | Si se necesita un orden, se puede agregar un mecanismo de prioridad |
| Problemas de rendimiento | Cuando hay muchos observadores, la notificación puede afectar el rendimiento | Usar notificación asíncrona o actualizaciones por lotes |
Escenarios de aplicación práctica
1. Manejo de eventos GUI
Ejemplo
button.on_click(handler1) # Registrar observador de eventos de clic
button.on_click(handler2) # Registrar otro observador
# Al hacer clic en el botón, todos los manejadores registrados serán llamados
2. Sistema de cola de mensajes
- El productor publica mensajes
- Múltiples consumidores se suscriben y procesan mensajes
3. Sistema de monitoreo de datos
- Cambio de estado de la fuente de datos
- Múltiples servicios de monitoreo reciben la notificación y toman medidas
4. Desarrollo de juegos
- Sistema de eventos del juego
- Los elementos de la interfaz de usuario (UI) responden a los cambios de estado del juego
Mejores prácticas y consideraciones
1. Usar referencias débiles para evitar fugas de memoria
Ejemplo
class SafeSubject(Subject):
def __init__(self):
self._observers = weakref.WeakSet() # Usar un conjunto de referencias débiles
def register_observer(self, observer: Observer):
self._observers.add(observer)
2. Notificación asíncrona para mejorar el rendimiento
Ejemplo
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. Agregar manejo de errores
Ejemplo
for observer in self._observers.copy(): # Usar una copia para evitar problemas de modificación
try:
observer.update(self._message)
except Exception as e:
print(f"Error al actualizar el observador {observer}: {e}")
Resumen
El patrón observador es un patrón de diseño muy importante en Python, que proporciona una forma elegante de implementar comunicación débilmente acoplada entre objetos. Al estudiar este artículo, deberías poder:
- Comprender los conceptos fundamentales del patrón observador y sus escenarios de aplicación
- Dominar los métodos básicos de implementación del patrón observador
- Aplicar el patrón observador en proyectos reales para resolver problemas
- Evitar las trampas comunes del patrón observador