Aplicar el patrón decorador en Python con protocolos tipados aporta flexibilidad y control. Aquí verás cómo envolver un PaymentService con lógica de logging sin tocar el comportamiento original, usando typing.Protocol, composición con wrap y una implementación limpia con @dataclass.
¿Cómo aplicar el patrón decorador al PaymentService paso a paso?
Para extender un servicio de pagos sin modificarlo, se define una interfaz con Protocol, se implementa la clase principal conforme a esa interfaz y luego se crea un decorador que delega en el servicio real, añadiendo logging.
Crear el protocolo del servicio con typing.Protocol.
Hacer que la clase PaymentService implemente ese protocolo.
Definir un protocolo para los decoradores con el atributo wrap.
Implementar un decorador de logging que imprime inicio y fin, y delega la lógica.
Instanciar el decorador en main envolviendo el servicio real.
¿Qué protocolos y clases intervienen en el diseño?
Se definen dos abstracciones clave. Primero, el protocolo del servicio que actúa como contrato: especifica firmas, no comportamiento. Segundo, el protocolo del decorador que replica esas firmas y agrega el atributo wrap tipado como PaymentServiceProtocol para delegar la ejecución. Esto asegura sustitución transparente y facilita pruebas.
Protocol: interfaz estructural que define métodos esperados.
wrap: composición para delegar en el servicio real.
Firmas replicadas: garantizan compatibilidad con el contrato original.
Importaciones mínimas: solo lo necesario para tipar y delegar.
¿Cómo mejora el logging sin alterar la lógica original?
El decorador PaymentServiceLogging implementa el protocolo del decorador, usa @dataclass para su construcción y añade prints de inicio y fin alrededor de cada método. La clave es llamar a self.wrap con los mismos parámetros y retornar la respuesta original, manteniendo el comportamiento intacto.
# logging_service.pyfrom dataclasses import dataclass
from decorator_protocol import PaymentServiceDecoratorProtocol
from service_protocol import PaymentServiceProtocol
@dataclassclassPaymentServiceLogging(PaymentServiceDecoratorProtocol): wrap: PaymentServiceProtocol
defprocess_transaction(self, customer_data, payment_data):print("start process transaction") response = self.wrap.process_transaction(customer_data, payment_data)print("finish process transaction")return response
defprocess_refund(self, transaction_id:str):print(f"start process refund: transaction_id={transaction_id}") response = self.wrap.process_refund(transaction_id)print("finish process refund")return response
defsetup_recurring(self, customer_data, payment_data):print("start setup recurring") response = self.wrap.setup_recurring(customer_data, payment_data)print("finish setup recurring")return response
¿Cómo se instancia el decorador en main?
# main.py (fragmento)from payment_service import PaymentService # implementación existentefrom logging_service import PaymentServiceLogging
service = PaymentService(...)service = PaymentServiceLogging(wrap=service)service.process_transaction(customer_data, payment_data)service.process_refund("1234")# si el servicio real no implementa refund, fallará.
En la demostración, el intento de refund sirve para ilustrar el flujo decorado, aunque el servicio real no tenga ese procesador. La idea central se mantiene: extender funcionalidad (logging y depuración) sin modificar el código del servicio.
¿Qué habilidades y keywords refuerzas con este patrón?
Diseño con Protocol como interfaz estructural.
Abstracción y contrato de métodos.
Composición con wrap para delegación.
Extensibilidad sin modificar comportamiento original.
Logging y debugging de transacciones.
Uso de @dataclass para instanciación clara.
¿De qué otras formas aplicarías el patrón decorador en tu PaymentService o en otras capas? Comparte tus ideas y casos en los comentarios.
Demasiado confusa la implementación del patrón. Imposible seguir el hilo de lo que hace con tantos protocol y service. El ejemplo no es bueno para ilustrar el patrón de diseño.
de verdad que esto no se entiende absolutamente nada.
Completamente!
Cual es la ventaja de hacerlo así y no de la manera nativa de la que están definidos los decoradores en Python? Siento que la legilidad de esos está mejor y mas pythonic, pero de pronto estos ofrecen alguna ventaja sobre esos
Un @decorador de python afecta solo la Función o el método de una clase
El Class Decorador, afecta toda la clase!
Ahora con un @decorador también podrías afectar todos los métodos de una clase.
Pero siento que es más declartivo hacer la creación de la instacia del Decorador que envuelve a la Instancia de la clase que queremos modificar...
Más fácil de leer, más claro...
Recuerda que el mejor código no es el más sofisticado sino el que sea más fácil de entender!
La ventaja de usar el patrón decorador de esta forma (basado en clases e interfaces/protocolos) es que puedes agregar lógica compleja a los objetos existentes sin modificar su estructura, permitiendo mayor flexibilidad y mantenibilidad.
Para que quede claro
El Patrón Decorador (con clases y envoltura de objetos):
Propósito: Añadir responsabilidades a instancias de objetos de forma dinámica.
Cuándo usarlo: Cuando necesitas añadir estado al decorador (ej. una conexión a caché) o cuando quieres decidir qué decoradores aplicar y en qué orden en tiempo de ejecución. Es más flexible y potente para modificar el comportamiento de un objeto completo.
Los Decoradores de Python (@):
Propósito: Añadir un comportamiento transversal a funciones, métodos o clases en el momento en que se definen.
Cuándo usarlo: Para preocupaciones transversales sin estado como logging, validación de permisos, caching simple. Es más limpio, más legible y más "pythónico" para estos casos de uso.
entonces, este patrón es un decorador más "sofisticado/robusto" podría decirse?
no necesariamente, el patrón lo que permite es extender la funcionalidad base sin hacer modificaciones al código, no es más sofisticado en verdad.
De hecho lo que se ve en la clase se puede crear con decoradores de Python sin problema
entiendo que es como un python decorator pero en vez que sea para funciones o métodos, son para toda una clase. Así puede agregar funcionalidades a todos los métodos de la clase decorada.
Un @decorador de python afecta solo la Función o el método de una clase
El Class Decorador, afecta toda la clase!
Ahora con un @decorador también podrías afectar todos los métodos de una clase.
Pero siento que es más declartivo hacer la creación de la instacia del Decorador que envuelve a la Instancia de la clase que queremos modificar...
Más fácil de leer, más claro...
Recuerda que el mejor código no es el más sofisticado sino el que sea más fácil de entender!
Implementemos el **Patrón Decorator** para un sistema de pagos. Supongamos que tienes un servicio base que procesa pagos, y quieres añadir funcionalidades como registro de logs, validación antifraude y notificaciones sin modificar la clase original.
### Paso 1: Clase Base para el Servicio de Pagos
Define una interfaz común para el servicio de pagos que será implementada por la clase base y los decoradores.
class PaymentService(ABC): @abstractmethod def process\_payment(self, amount: float, currency: str): pass```
Implementa la funcionalidad básica en una clase concreta.
```pythonclass BasicPaymentService(PaymentService): def process\_payment(self, amount: float, currency: str): print(f"Procesando pago de {amount} {currency}.")```
\### Paso 2: Crear Decoradores para Extender Funcionalidades
Los decoradores implementan la misma interfaz y envuelven la instancia del servicio de pagos.
\#### Decorador de Registro de Logs
```pythonclass LoggingDecorator(PaymentService): def \_\_init\_\_(self, payment\_service: PaymentService): self.payment\_service = payment\_service
def process\_payment(self, amount: float, currency: str): print(f"\[Logger]: Procesando un pago de {amount} {currency}.") self.payment\_service.process\_payment(amount, currency)```
\#### Decorador de Validación Antifraude
```pythonclass FraudCheckDecorator(PaymentService): def \_\_init\_\_(self, payment\_service: PaymentService): self.payment\_service = payment\_service
def process\_payment(self, amount: float, currency: str): if self.\_is\_fraudulent(amount): print("\[FraudCheck]: Pago marcado como fraudulento. Transacción detenida.") else: print("\[FraudCheck]: Pago validado.") self.payment\_service.process\_payment(amount, currency)
def \_is\_fraudulent(self, amount: float) -> bool: # Simulación de reglas antifraude: Ejemplo, montos mayores a 10,000 son sospechosos return amount > 10000```
\#### Decorador de Notificación
```pythonclass NotificationDecorator(PaymentService): def \_\_init\_\_(self, payment\_service: PaymentService): self.payment\_service = payment\_service
def process\_payment(self, amount: float, currency: str): self.payment\_service.process\_payment(amount, currency) self.\_send\_notification(amount, currency)
def \_send\_notification(self, amount: float, currency: str): print(f"\[Notification]: Enviando notificación por el pago de {amount} {currency}.")```
\### Paso 3: Componer el Servicio Decorado
Crea un servicio de pagos básico y añade las funcionalidades deseadas mediante decoradores.
```python# Servicio de pagos básicobasic\_service = BasicPaymentService()
\# Añadir funcionalidad de registro de logslogged\_service = LoggingDecorator(basic\_service)
\# Añadir validación antifraudefraud\_checked\_service = FraudCheckDecorator(logged\_service)
\# Añadir notificaciónfull\_service = NotificationDecorator(fraud\_checked\_service)
\# Usar el servicio decoradoprint("=== Pago 1 ===")full\_service.process\_payment(5000, "USD")
print("\n=== Pago 2 ===")full\_service.process\_payment(15000, "USD")```
\### Resultado Esperado
\*\*Pago 1 (válido):\*\*```\[Logger]: Procesando un pago de 5000 USD.\[FraudCheck]: Pago validado.Procesando pago de 5000 USD.\[Notification]: Enviando notificación por el pago de 5000 USD.```
\*\*Pago 2 (fraudulento):\*\*```\[Logger]: Procesando un pago de 15000 USD.\[FraudCheck]: Pago marcado como fraudulento. Transacción detenida.```
\### Ventajas del Patrón Decorator en este Contexto
1\. \*\*Modularidad\*\*: Cada funcionalidad (logs, antifraude, notificaciones) está separada en su propio decorador, lo que facilita su mantenimiento.2. \*\*Flexibilidad\*\*: Puedes añadir, quitar o combinar decoradores según sea necesario, sin modificar las clases originales.3. \*\*Cumple con SOLID\*\*: - \*\*Responsabilidad Única (SRP)\*\*: Cada decorador tiene una responsabilidad clara. - \*\*Abierto/Cerrado (OCP)\*\*: Puedes extender el comportamiento sin modificar las clases existentes.
\### Extensión del Ejemplo
\#### Añadir un Decorador para Conversión de Moneda
Si necesitas convertir el monto a una moneda específica antes de procesarlo:
```pythonclass CurrencyConverterDecorator(PaymentService): def \_\_init\_\_(self, payment\_service: PaymentService, exchange\_rate: float): self.payment\_service = payment\_service self.exchange\_rate = exchange\_rate
def process\_payment(self, amount: float, currency: str): converted\_amount = amount \* self.exchange\_rate print(f"\[CurrencyConverter]: Convertido {amount} {currency} a {converted\_amount} USD.") self.payment\_service.process\_payment(converted\_amount, "USD")```
Usa este decorador antes de los demás si necesitas convertir monedas.
\### Conclusión
El \*\*Patrón Decorator\*\* permite agregar funcionalidades como logs, validaciones y notificaciones al servicio de pagos de manera limpia y extensible. Es especialmente útil para aplicaciones empresariales donde los requisitos cambian con frecuencia y los servicios deben ser altamente configurables.