Implementa notificaciones de pagos en Python con confianza: aquí verás cómo aplicar el patrón Observer para informar a múltiples sistemas cuando un pago es exitoso o fallido, usando listeners, manager, Protocol, genéricos y el patrón builder para un diseño limpio y extensible.
¿Qué resuelve el patrón observer en el sistema de pagos?
El objetivo es notificar a distintos sistemas ante dos eventos: pago exitoso y pago fallido. Para lograrlo, se define una interfaz de listener con un método único, notify, y un manager que centraliza la suscripción, desuscripción y la propagación del evento a todos los listeners interesados.
Interfaz mínima y clara: un solo método notify que recibe un evento genérico de tipo T.
Gestión centralizada: un Listeners Manager con métodos subscribe, unsubscribe y notify_all.
Extensibilidad: agregar nuevos listeners es simple y no rompe el código existente.
Integración con el servicio: el builder configura el manager y sus listeners al construir la clase principal.
¿Cómo se implementa el patrón observer paso a paso?
A continuación, una guía práctica basada en el código explicado.
¿Cómo se define la interfaz listener con Protocol y genéricos?
Se crea una carpeta listeners/ y un archivo listener.py que declara el Protocol con un genérico T y el método notify.
Uso de typing.Protocol para definir la forma del listener.
Genérico T para aceptar cualquier tipo de evento sin perder tipado.
El método requiere self y no retorna valor.
¿Cómo gestionar suscripciones con un manager usando dataclass?
Se crea manager.py con un atributo lista de listeners y tres métodos: subscribe, unsubscribe y notify_all. La lista se inicializa vacía con field(default_factory=list).
# listeners/manager.pyfrom dataclasses import dataclass, field
from typing import List, TypeVar
from.listener import Listener
T = TypeVar("T")@dataclassclassListenersManager: listeners: List[Listener[T]]= field(default_factory=list)defsubscribe(self, listener: Listener[T])->None: self.listeners.append(listener)defunsubscribe(self, listener: Listener[T])->None: self.listeners.remove(listener)defnotify_all(self, event: T)->None:for listener in self.listeners: listener.notify(event)
Puntos clave:
dataclass simplifica la construcción de la clase.
default_factory=list evita pasar la lista por parámetro y previene listas mutables compartidas.
notify_all itera y llama a notify en cada listener suscrito.
¿Cómo crear un listener concreto para contabilidad?
Se implementa un listener sencillo, por ejemplo, AccountabilityListener, que imprime el evento recibido.
# listeners/accountability_listener.pyfrom.listener import Listener
classAccountabilityListener(Listener[object]):defnotify(self, event:object)->None:print("Notificando el evento:", event)
Puntos clave:
Implementa notify con la lógica específica (aquí, un print controlado).
El tipo de evento puede ajustarse según el dominio.
¿Cómo integrarlo en el servicio con el patrón builder?
Se agrega al service un atributo de tipo ListenersManager y un método del builder para configurarlo y suscribir listeners por defecto.
Implementar el **Patrón Observer** implica crear un sistema donde un objeto central (*subject*) notifica automáticamente a varios objetos suscritos (*observers*) cada vez que su estado cambia. Veamos cómo implementarlo paso a paso en Python.
### Escenario: Sistema de Notificaciones para Pedidos
Vamos a construir un ejemplo donde un sistema de pedidos notifica a varios servicios (correo electrónico, SMS, registro en el log) cuando se procesa un pedido.
### Paso 1: Crear el Subject
El *subject* gestiona la lista de *observers* y los notifica cuando ocurre un cambio.
def attach(self, observer): """Suscribe un observer al subject.""" self.\_observers.append(observer)
def detach(self, observer): """Elimina un observer del subject.""" self.\_observers.remove(observer)
def notify(self, message): """Notifica a todos los observers.""" for observer in self.\_observers: observer.update(message)```
\### Paso 2: Crear la Interfaz del Observer
Define una clase base para los *\*observers\**. Esto asegura que todos implementen el método `update`.
```pythonfrom abc import ABC, abstractmethod
class Observer(ABC): @abstractmethod def update(self, message): """Define cómo reaccionará el observer al mensaje.""" pass```
\### Paso 3: Crear Observers Concretos
Cada *\*observer\** define su propia implementación del método `update`.
```pythonclass EmailNotifier(Observer): def update(self, message): print(f"EmailNotifier: Enviando email con el mensaje: '{message}'")
class SMSNotifier(Observer): def update(self, message): print(f"SMSNotifier: Enviando SMS con el mensaje: '{message}'")
class LogNotifier(Observer): def update(self, message): print(f"LogNotifier: Registrando en el log: '{message}'")```
\### Paso 4: Crear un Subject Concreto
Crea una clase que represente el *\*subject\**. En este caso, un sistema de pedidos.
```pythonclass OrderSystem(Subject): def process\_order(self, order\_id): print(f"Procesando pedido {order\_id}...") self.notify(f"Pedido {order\_id} ha sido procesado con éxito.")```
\### Paso 5: Integrar Todo
Conecta los *\*observers\** al *\*subject\** y prueba el sistema.
```python# Crear el sistema de pedidosorder\_system = OrderSystem()
\# Crear los observersemail\_notifier = EmailNotifier()sms\_notifier = SMSNotifier()log\_notifier = LogNotifier()
\# Suscribir los observers al sistema de pedidosorder\_system.attach(email\_notifier)order\_system.attach(sms\_notifier)order\_system.attach(log\_notifier)
\# Procesar un pedidoorder\_system.process\_order("12345")
\# Eliminar un observerorder\_system.detach(sms\_notifier)
\# Procesar otro pedidoorder\_system.process\_order("67890")```
\### Salida Esperada
\*\*Primer pedido (todos los observers suscritos):\*\*
```Procesando pedido 12345...EmailNotifier: Enviando email con el mensaje: 'Pedido 12345 ha sido procesado con éxito.'SMSNotifier: Enviando SMS con el mensaje: 'Pedido 12345 ha sido procesado con éxito.'LogNotifier: Registrando en el log: 'Pedido 12345 ha sido procesado con éxito.'```
\*\*Segundo pedido (SMSNotifier eliminado):\*\*
```Procesando pedido 67890...EmailNotifier: Enviando email con el mensaje: 'Pedido 67890 ha sido procesado con éxito.'LogNotifier: Registrando en el log: 'Pedido 67890 ha sido procesado con éxito.'```
\### Ventajas del Patrón Observer
1\. \*\*Desacoplamiento\*\*: Los *\*subjects\** no necesitan saber cómo funcionan los *\*observers\**.2. \*\*Flexibilidad\*\*: Puedes añadir o eliminar *\*observers\** dinámicamente.3. \*\*Reutilización\*\*: Los *\*observers\** son reutilizables en diferentes sistemas.
\### Casos de Uso Reales
\- \*\*Interfaces gráficas de usuario\*\*: Actualización de vistas cuando el modelo cambia.- \*\*Sistemas de eventos\*\*: Publicación/suscripción (*\*pub-sub\**), como en servicios de mensajería.- \*\*Notificaciones en tiempo real\*\*: Alertas en aplicaciones.
\### Conclusión
El \*\*Patrón Observer\*\* es ideal para sistemas en los que un cambio en un objeto afecta a otros. Su implementación en Python es directa y altamente extensible, lo que lo hace una herramienta poderosa para sistemas desacoplados y dinámicos.
No entiendo porque ponerle un genérico junto a la declaración de la clase. ¿No es lo mismo declarar los genéricos en los atributos o los métodos? ¿Por qué declarar la clase genérica?
Declarar un genérico en la clase (Listener[T]) permite que la interfaz Listener funcione con cualquier tipo de evento (T) de forma segura en Python. Esto asegura que todos los métodos de esa clase usen el mismo tipo T consistentemente. En cambio, si solo se declara en métodos o atributos, la coherencia de tipos se perdería.
!Estructura del patrón de diseño Observer
¿En qué clase de qué curso se ven los Genéricos, no recuerdo haberlos visto y me sigo sin entenderlos bien?
No hay una clase particular donde se hable de generics, se utilizan para poder modelar de mejor forma la clase.
Al final los generics son una forma de decir "puede ser de cualquier tipo". En Python 3.12 cambia la forma es la que especifican los generics para que sean más fáciles de declarar que en versiones anteriores.
Qué dudas puntuales tienes para ayudarte?
Bueno este fue la forma en que resolvi el reto del profesor para notificar que hay pago fallido
defprocess_transaction( self, customer_data: CustomerData, payment_data: PaymentData
)-> PaymentResponse: self.customer_validator.validate(customer_data) self.payment_validator.validate(payment_data) payment_response = self.payment_processor.process_transaction( customer_data, payment_data
)if payment_response.status =="success": self.listeners.notifyAll(f'Pago exitoso - ID de transacción: {payment_response.transaction_id}') self.notifier.send_confirmation(customer_data)else: self.listeners.notifyAll(f'Error en el pago - Motivo: {payment_response.message}') self.logger.log_transaction(customer_data, payment_data, payment_response)return payment_response
El [T] en AccountabilityListener indica que es un tipo genérico que permite que el listener reciba eventos de cualquier tipo. Esto es diferente de la herencia, que crea una relación entre clases, permitiendo que una clase derive de otra. Un ejemplo simple sería: