Strategy Pattern para pagos en Python

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

Contenido del curso

Principios SOLID

Patrones de Diseño

Resumen

Seleccionar la estrategia correcta en tiempo de ejecución es una de las ventajas más potentes del Strategy Pattern. En un procesador de pagos construido en Python, esta capacidad permite intercambiar notificadores —correo electrónico o mensaje de texto— sin modificar la clase principal del servicio, manteniendo un diseño flexible y extensible.

¿Cómo se detecta cuándo aplicar el Strategy Pattern?

Una señal clara para aplicar este patrón es cuando un atributo de la clase de alto nivel tiene como tipo de dato una interfaz (o en Python, un protocol) [01:08]. Esto indica que múltiples clases implementan esa interfaz y cada una resuelve un caso puntual con una estrategia diferente.

En el servicio de pagos, la clase PaymentService define su atributo notifier con el tipo NotifierProtocol. Esto significa que cualquier clase que cumpla con ese protocolo puede ocupar ese lugar: un EmailNotifier, un SMSNotifier o cualquier implementación futura.

¿Cuál es el primer paso para implementar la estrategia?

El primer paso es crear un método que permita modificar la implementación de la estrategia en tiempo de ejecución [02:00]. Dentro de PaymentService se define el método set_notifier:

python def set_notifier(self, notifier: NotifierProtocol): self.notifier = notifier print("Cambiando la implementación del notificador")

Este método recibe una instancia que cumpla con NotifierProtocol y reemplaza el notificador actual. Es simple, pero es la pieza que habilita el intercambio dinámico.

¿Cómo se elige la estrategia correcta según el contexto?

El segundo paso consiste en crear una función que seleccione la estrategia adecuada para cada caso de uso [02:38]. En el archivo principal se define get_notifier_implementation, que recibe los datos del cliente y evalúa qué canal de notificación usar:

python def get_notifier_implementation(customer_data: CustomerData) -> NotifierProtocol: if customer_data.contact_info.phone: return get_sms_notifier() elif customer_data.contact_info.email: return get_email_notifier() raise Exception("No se puede elegir la estrategia correcta")

  • Si el contact_info contiene teléfono, se retorna un SMSNotifier [03:16].
  • Si contiene email, se retorna un EmailNotifier [03:50].
  • Si no hay ninguno de los dos, se levanta una excepción porque no existe una estrategia válida [04:12].

¿Cómo se encapsulan las estrategias disponibles?

Cada estrategia se encapsula en su propia función factoría para mantener el código limpio:

python def get_email_notifier() -> EmailNotifier: return EmailNotifier()

def get_sms_notifier() -> SMSNotifier: return SMSNotifier(gateway="sms_gateway_example")

Estas funciones se pueden invocar de forma independiente cuando se necesite cambiar la estrategia manualmente [06:30].

¿Cómo se intercambian estrategias en tiempo de ejecución?

Una vez seleccionada la estrategia inicial, el patrón permite reaccionar ante fallos y cambiar de notificador sin reiniciar el proceso [07:00]. Por ejemplo, si el envío por email falla, se captura la excepción y se cambia a SMS:

python email_notifier = get_email_notifier() sms_notifier = get_sms_notifier()

Estrategia inicial

service.set_notifier(email_notifier)

Si falla el email, cambiar a SMS

try: service.process_payment() except Exception: print("Cambio de estrategia de notificación a SMS") service.set_notifier(sms_notifier)

Este mecanismo ofrece dos beneficios principales:

  • Selección contextual: la estrategia se elige automáticamente según los datos del cliente.
  • Intercambio dinámico: ante un fallo, se sustituye la estrategia sin alterar la lógica del servicio.

Un detalle interesante es que la excepción lanzada cuando no hay teléfono ni email revela la oportunidad de crear una tercera estrategia [08:08]. Podría ser una notificación push o un registro en base de datos que garantice que el usuario reciba confirmación de su pago.

El mismo principio aplica al procesador de pagos: su atributo también es un protocolo, lo que abre la puerta a seleccionar dinámicamente entre diferentes métodos de pago [08:50]. ¿Cómo implementarías tú el Strategy Pattern para elegir el procesador de pagos correcto según el tipo de transacción? Comparte tu propuesta en los comentarios.