Hector Emanuel Barboza
Fuente:
!Estructura del patrón de diseño Chain Of Responsibility
Aplicar el patrón Chain of Responsibility a un servicio de pagos en Python mejora la organización de validaciones y facilita la extensión. Aquí verás cómo encapsular datos con Pydantic, crear un chain handler abstracto con ABC y dataclass, implementar un CustomerHandler y ajustar el servicio y el builder para validar en cadena.
¿Cómo estructurar el request y por qué usar Pydantic?
Encapsular en un solo objeto los datos que participan en la validación simplifica la firma de métodos y estandariza la entrada. Se define una clase request que hereda de BaseModel para asegurar tipos y validación básica.
from pydantic import BaseModel # Declarada en el módulo commons class Request(BaseModel): customer_data: CustomerData payment_data: PaymentData
¿Cómo definir el chain handler abstracto con ABC y dataclass?
La cadena requiere un eslabón común con comportamiento compartido: establecer el siguiente handler y un método handle obligado. Se combina ABC para el contrato, dataclass para el atributo next_handler, y Self para tipos recursivos. El método set_next retorna el propio siguiente eslabón para encadenar con fluidez.
@abstractmethod para handle.from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Optional, Self from commons import Request @dataclass class ChainHandler(ABC): next_handler: Optional[Self] = None def set_next(self, handler: Self) -> Self: self.next_handler = handler return handler @abstractmethod def handle(self, request: Request) -> None: ...
¿Cómo implementar CustomerHandler y conectar la cadena en el builder?
Este eslabón valida el customer con un validador existente. Si todo es correcto, delega al siguiente handler. Si ocurre un error, lo propaga. Se usa try/except porque el validador lanza excepciones cuando hay datos inválidos.
next_handler.from commons import Request from validators.handler import ChainHandler from Customer import CustomerValidator # Validador existente class CustomerHandler(ChainHandler): def handle(self, request: Request) -> None: validator = CustomerValidator() try: validator.validate(request.customer_data) if self.next_handler: self.next_handler.handle(request) except Exception as exc: print("Fallo en validación de customer.") raise exc
¿Cómo se adapta el servicio y el protocolo?
El servicio deja de depender de múltiples validadores y recibe un único ChainHandler. Construye el request y ejecuta la cadena. Ante error, imprime y relanza la excepción para frenar el proceso.
from commons import Request # Dentro del servicio req = Request(customer_data=customer_data, payment_data=payment_data) try: self.validator.handle(req) except Exception as exc: print("Fallo en las validaciones.") raise exc
handle.Request y ejecutar la cadena.¿Cómo configurar la cadena en el builder?
El builder expone un método para armar la cadena. El primer eslabón se guarda como self.validator. El método retorna self para encadenar configuraciones y el build inyecta el validador en el servicio.
from typing import Optional, Self from validators.handler import ChainHandler from validators.customer_handler import CustomerHandler class PaymentServiceBuilder: validator: Optional[ChainHandler] = None def set_chain_of_validations(self) -> Self: ch1 = CustomerHandler() ch2 = CustomerHandler() # Ejemplo: encadenar otro eslabón ch1.set_next(ch2) self.validator = ch1 return self def build(self) -> PaymentService: return PaymentService(validator=self.validator)
self para continuar la configuración.validator puede ser None hasta configurarlo.¿Cómo implementarías el handler para payment_data y cómo encadenarías toda la validación en el builder? Comparte tu enfoque y decisiones en los comentarios.
El **Patrón Chain of Responsibility** es ideal para implementar un flujo flexible de validaciones donde cada paso puede aceptar, rechazar o delegar una solicitud al siguiente manejador en la cadena. Supongamos que estamos construyendo un sistema para validar formularios de usuario con múltiples pasos de validación, como comprobar si los datos están completos, si el email tiene un formato válido y si la contraseña cumple los criterios de seguridad. ### Paso 1: Clase Base para los Manejadores La clase base define una estructura común para todos los pasos de validación.
class Validador(ABC): def \_\_init\_\_(self): self.siguiente = None def establecer\_siguiente(self, validador): """Establece el siguiente validador en la cadena.""" self.siguiente = validador return validador @abstractmethod def manejar(self, datos): """Intenta validar los datos o pasa al siguiente validador.""" pass``` \### Paso 2: Validadores Concretos Cada validador implementa su propia lógica de validación. ```pythonclass ValidadorDatosCompletos(Validador): def manejar(self, datos): if not datos.get("email") or not datos.get("password"): return "Error: Datos incompletos." elif self.siguiente: return self.siguiente.manejar(datos) return "Validación completada." class ValidadorFormatoEmail(Validador): def manejar(self, datos): if "@" not in datos.get("email", ""): return "Error: Formato de email inválido." elif self.siguiente: return self.siguiente.manejar(datos) return "Validación completada." class ValidadorSeguridadPassword(Validador): def manejar(self, datos): password = datos.get("password", "") if len(password) < 8 or not any(char.isdigit() for char in password): return "Error: Contraseña no cumple los criterios de seguridad." elif self.siguiente: return self.siguiente.manejar(datos) return "Validación completada."``` \### Paso 3: Configuración de la Cadena de Validación Conectamos los validadores en un flujo secuencial. ```python# Crear los validadoresvalidador\_datos\_completos = ValidadorDatosCompletos()validador\_formato\_email = ValidadorFormatoEmail()validador\_seguridad\_password = ValidadorSeguridadPassword() \# Configurar la cadenavalidador\_datos\_completos.establecer\_siguiente(validador\_formato\_email).establecer\_siguiente(validador\_seguridad\_password)``` \### Paso 4: Probar el Sistema de Validación Validamos diferentes conjuntos de datos usando la cadena configurada. ```python# Datos de pruebacasos = \[ {"email": "", "password": ""}, # Datos incompletos {"email": "usuario", "password": "12345678"}, # Formato de email inválido {"email": "usuario@example.com", "password": "short"}, # Contraseña insegura {"email": "usuario@example.com", "password": "secure123"} # Datos válidos] \# Probar cada casofor i, caso in enumerate(casos, start=1): print(f"Caso {i}: {caso}") resultado = validador\_datos\_completos.manejar(caso) print(f"Resultado: {resultado}\n")``` \### Salida Esperada ```Caso 1: {'email': '', 'password': ''}Resultado: Error: Datos incompletos. Caso 2: {'email': 'usuario', 'password': '12345678'}Resultado: Error: Formato de email inválido. Caso 3: {'email': 'usuario@example.com', 'password': 'short'}Resultado: Error: Contraseña no cumple los criterios de seguridad. Caso 4: {'email': 'usuario@example.com', 'password': 'secure123'}Resultado: Validación completada.``` \### Ventajas del Patrón Chain of Responsibility para Validaciones 1\. \*\*Modularidad\*\*: Cada validador tiene una única responsabilidad, facilitando la lectura y el mantenimiento.2. \*\*Flexibilidad\*\*: Es fácil agregar o quitar validadores sin afectar el sistema general.3. \*\*Reutilización\*\*: Los validadores pueden ser usados en otras cadenas de validación. \### Casos de Uso Reales \- Validación de formularios de usuario.- Procesamiento de reglas de negocio en sistemas complejos.- Flujos de autorización en aplicaciones. \### Conclusión El \*\*Patrón Chain of Responsibility\*\* es una solución elegante para implementar flujos de validación escalables y flexibles. En este ejemplo, los datos pasan por una serie de validadores, cada uno con la posibilidad de manejar la solicitud o delegarla al siguiente en la cadena. Este enfoque mejora la modularidad y el mantenimiento del código.
¡Saludos! 👋🏻
Me tomé el atrevimiento de recrear tu código y volverlo a colocar acá para que sea más fácil su lectura. Aquí lo comparto:
from abc import ABC, abstractmethod # Paso 1: Clase Base para los Manejadores # La clase base define una estructura común para todos los pasos de validación. class Validador(ABC): def __init__(self): self.siguiente = None def establecer_siguiente(self, validador): """Establece el siguiente validador en la cadena.""" self.siguiente = validador return validador @abstractmethod def manejar(self, datos): """Intenta validar los datos o pasa al siguiente validador.""" pass # Paso 2: Validadores Concretos # Cada validador implementa su propia lógica de validación. class ValidadorDatosCompletos(Validador): def manejar(self, datos): if not datos.get("email") or not datos.get("password"): return "Error: Datos incompletos." elif self.siguiente: return self.siguiente.manejar(datos) return "Validación completada." class ValidadorFormatoEmail(Validador): def manejar(self, datos): if "@" not in datos.get("email", ""): return "Error: Formato de email inválido." elif self.siguiente: return self.siguiente.manejar(datos) return "Validación completada." class ValidadorSeguridadPassword(Validador): def manejar(self, datos): password = datos.get("password", "") if len(password) < 8 or not any(char.isdigit() for char in password): return "Error: Contraseña no cumple los criterios de seguridad." elif self.siguiente: return self.siguiente.manejar(datos) return "Validación completada." # Paso 3: Configuración de la Cadena de Validación # Conectamos los validadores en un flujo secuencial. # Crear los validadores validador_datos_completos = ValidadorDatosCompletos() validador_formato_email = ValidadorFormatoEmail() validador_seguridad_password = ValidadorSeguridadPassword() # Configurar la cadena validador_datos_completos.establecer_siguiente(validador_formato_email).establecer_siguiente(validador_seguridad_password) # Paso 4: Probar el Sistema de Validación # Validamos diferentes conjuntos de datos usando la cadena configurada. # Datos de prueba casos = [ {"email": "", "password": ""}, {"email": "usuario", "password": "12345678"}, {"email": "usuario@example.com", "password": "short"}, {"email": "usuario@example.com", "password": "secure123"} ] # {"email": "", "password": ""} Datos incompletos # {"email": "usuario", "password": "12345678"} Formato de email inválido # {"email": "usuario@example.com", "password": "short"} Contraseña insegura # {"email": "usuario@example.com", "password": "secure123"} Datos válidos # Probar cada caso for i, caso in enumerate(casos, start=1): print(f"Caso {i}: {caso}") resultado = validador_datos_completos.manejar(caso) print(f"Resultado: {resultado}\n") # Salida Esperada: #Caso 1: {'email': '', 'password': ''} Resultado: Error: Datos incompletos. #Caso 2: {'email': 'usuario', 'password': '12345678'} Resultado: Error: Formato de email inválido. #Caso 3: {'email': 'usuario@example.com', 'password': 'short'} Resultado: Error: Contraseña no cumple los criterios de seguridad. #Caso 4: {'email': 'usuario@example.com', 'password': 'secure123'} Resultado: Validación completada. # Ventajas del Patrón Chain of Responsibility para Validaciones # 1.Modularidad: Cada validador tiene una única responsabilidad, facilitando la lectura y el mantenimiento. # 2.Flexibilidad: Es fácil agregar o quitar validadores sin afectar el sistema general. # 3.Reutilización: Los validadores pueden ser usados en otras cadenas de validación. # Casos de Uso Reales # Validación de formularios de usuario. # Procesamiento de reglas de negocio en sistemas complejos. # Flujos de autorización en aplicaciones. # Conclusión # El Patrón Chain of Responsibility es una solución elegante para implementar flujos de validación escalables y flexibles. # En este ejemplo, los datos pasan por una serie de validadores, cada uno con la posibilidad de manejar la solicitud o # delegarla al siguiente en la cadena. Este enfoque mejora la modularidad y el mantenimiento del código.
Esta muy buena esta explicación. 👏🏻
Así definí el validador para el PaymentData:
from chain_handler import ChainHandler from commons import Request from payment import PaymentDataValidator class PaymentHandler(ChainHandler): def handle(self, request: Request): validator = PaymentDataValidator() try: validator.validate(request.payment_data) if self._nex_handler: self._nex_handler.handle(request) except Exception as e: print('error') raise e
Así construí la Chain of Resposabilities dentro del builder:
def set_chain_of_validations(self) -> Self: customer_handler = CustomerHandler() payment_handler = PaymentHandler() customer_handler.set_next(payment_handler) self.validator = customer_handler return self
Lo probé y me funcionó!
Saludos a todos los que llegaron hasta acá y si no sabían absolutamente nada de cómo funcionan de alguna forma los procesadores de pago, pues ya sabemos algo. 😁
Hector Emanuel Barboza
Fuente:
!Estructura del patrón de diseño Chain Of Responsibility
juan esteban colcombet
Pregunta mas filosofica que otra cosa (y quiero ir por lo absurdo para entender mejor la idea), si todos los códigos que hacemos son de tomar una entrada, realizar N procesos en el medio, y devolver una salida (me estoy tomando muchas libertades al decir esto, espero que se entienda). Por que no ocupamos este patron de diseño (chain of responsibility) todo el tiempo?
Platzi
Este patrón se aplica cuando se necesita procesar una solicitud a través de varios pasos y cada uno puede decidir finalizar el proceso; no siempre es necesario para procesos más sencillos o diferentes.
Franco Manca
Porque cuando uno empieza a programar no suele ver patrones de diseños y SOLID, que es lo que enseña este curso.
Estas buenas practicas se suelen implementar cuando uno adquiere experiencia en el mundo de la programación y necesita/entiende que debe hacer mas efeciente, legible y facil de manejar su código...
- Ejemplo: Llevo mas de 5 años programando y ahora con experiencia y entender que hay código dificil de manejar, llevar un pequeño equipo y querer querer aportar a librerias Open Source necesito entender como implementar correctamente SOLID y saber de patrones de diseño, para ser mejor programador/ingeniero.
Juan Camilo Suarez
Hola, esta fue mi solución al reto:
from chain_handler import ChainHandler from payment import PaymentDataValidator from commons import Request class PaymentHandler(ChainHandler): def handle(self, request:Request): validator = PaymentDataValidator() #tomar la decisión de pasar al siguiente eslabón o no try: valid = validator.validate(request.payment_data) # si no levanta una excepción, pasa al siguiente eslabón if self._next_handler: # solo si el siguiente eslabón existe: self._next_handler.handle(request) except Exception as e: print(e) # payment_validator ya levanta las excepciones. raise e ``````   Y así lo implementé el **builder.py:**   ```python def set_payment_validation(self) -> Self: customer = CustomerHandler() payment = PaymentDataValidator() handle = ChainHandler() handle.handle(customer) handle.set_next(payment) self.validators = handle return self
@camilocsoto