Builder pattern para servicios de pagos

Clase 22 de 27Curso de Patrones de Diseño y SOLID en Python

Resumen

Aplicar el patrón builder en Python permite construir un servicio de pagos flexible, seguro y fácil de mantener. Aquí verás cómo crear un PaymentServiceBuilder con atributos opcionales, métodos encadenables, validaciones robustas y la integración con un factory para seleccionar el procesador de pago correcto y un notificador por email o SMS.

¿Qué problema resuelve el builder pattern en un servicio de pagos?

El escenario requiere muchas configuraciones previas a la instanciación del servicio. El builder organiza estos pasos, hace explícitas las dependencias y evita errores de inicialización.

  • Muchos pasos previos: procesador, notificador, validadores y logger.
  • Atributos opcionales con valor None hasta completar la configuración.
  • Encadenamiento de métodos que retornan self para una construcción fluida.
  • Validación previa con un método build que asegura dependencias completas.
  • Combinación de patrones: factory para seleccionar el payment processor y builder para orquestar la construcción.

¿Cómo implementar el builder del PaymentService paso a paso?

La clase del builder replica los atributos del servicio final, pero los define como opcionales con valor por defecto None. Se usa data class para simplificar.

¿Cómo se declaran atributos opcionales con data class?

from dataclasses import dataclass from typing import Optional, Self # Protocolos/clases referenciadas como tipos (ya importadas en tu proyecto) # PaymentProcessorProtocol, TransactionLogger, PaymentDataValidator, CustomerDataValidator @dataclass class PaymentServiceBuilder: logger: Optional[TransactionLogger] = None payment_validator: Optional[PaymentDataValidator] = None customer_validator: Optional[CustomerDataValidator] = None payment_processor: Optional[PaymentProcessorProtocol] = None notifier: Optional[object] = None # EmailNotifier o SMSNotifier
  • Mismos atributos que el servicio final pero con Optional y None.
  • Importaciones de protocolos y clases necesarias para tipado y funcionamiento.

¿Cómo encadenar métodos set y qué se instancia directo?

Logger y validadores no dependen de abstracciones externas, se instancian directo.

def setLogger(self) -> Self: self.logger = TransactionLogger() return self def setPaymentValidator(self) -> Self: self.payment_validator = PaymentDataValidator() return self def setCustomerValidator(self) -> Self: self.customer_validator = CustomerDataValidator() return self
  • Cada set retorna self: permite encadenar llamadas.
  • Instanciación directa: sin lógica adicional.

¿Cómo integrar factory y notificador según datos?

Para el procesador se usa un factory; para el notificador, reglas con if.

# from commons import PaymentData # from factory import PaymentProcessorFactory def setPaymentProcessor(self, payment_data: PaymentData) -> Self: self.payment_processor = PaymentProcessorFactory.create_payment_processor(payment_data) return self # from commons import CustomerData # importa EmailNotifier y SMSNotifier según tu estructura def setNotifier(self, customer_data: CustomerData) -> Self: contact = customer_data.contact_info if contact.email: self.notifier = EmailNotifier() return self if contact.phone_number: self.notifier = SMSNotifier(gateway="my custom gateway") return self raise ValueError("No se puede seleccionar clase de notificación.")
  • Selección por factory: decide el mejor procesador según el tipo de pago.
  • Reglas de notificación: email o SMS según contact info.
  • Excepción temprana: si no hay email ni teléfono.

¿Qué valida build y cómo maneja errores?

El método build verifica que las dependencias críticas estén presentes antes de instanciar el servicio. Si falta algo, informa exactamente qué falta.

# from service import PaymentService def build(self) -> PaymentService: required = [ self.payment_processor, self.notifier, self.customer_validator, self.payment_validator, self.logger, ] if not all(required): # pares (nombre, valor) para identificar faltantes pairs = [ ("payment_processor", self.payment_processor), ("notifier", self.notifier), ("customer_validator", self.customer_validator), ("payment_validator", self.payment_validator), ("logger", self.logger), ] missing = [name for name, value in pairs if value is None] raise ValueError(f"missing dependencies: {missing}") # El editor puede advertir por tipos Optional; ya fueron validados arriba. return PaymentService( payment_processor=self.payment_processor, payment_validator=self.payment_validator, customer_validator=self.customer_validator, notifier=self.notifier, logger=self.logger, )
  • Chequeo con all: si hay None en requeridos, falla la validación.
  • Lista missing: indica en claro qué dependencia falta.
  • Instanciación final segura: solo ocurre con dependencias completas.

¿Cómo usar el builder y qué ocurre si falta un set?

Encadena sets y finaliza con build. Si omites un set requerido, build lanza ValueError con la dependencia faltante.

# from builder import PaymentServiceBuilder # payment_data y customer_data ya preparados builder = PaymentServiceBuilder() service = ( builder .setLogger() .setPaymentValidator() .setCustomerValidator() .setPaymentProcessor(payment_data) .setNotifier(customer_data) .build() ) # Si omites, por ejemplo, .setPaymentValidator(): # ValueError: missing dependencies: ['payment_validator']
  • Encadenamiento fluido: claridad y orden en la configuración.
  • Errores claros: facilitan depurar con breakpoints y corregir rápido.
  • Extensión sugerida: añade sets para procesadores de refund y recurring.

¿Te animas a ir más allá? agrega un nuevo método en el builder que decore el comportamiento del PaymentService usando un decorator visto antes y comparte tu solución en comentarios.

      Builder pattern para servicios de pagos