Strategy Pattern para pagos en Python

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

Resumen

Aplicar el patrón estrategia (Strategy Pattern) en un servicio de pagos en Python permite elegir la mejor forma de notificar y cambiarla al vuelo cuando el contexto lo exige. Aquí verás cómo usar un Notifier Protocol para tipar el atributo de notificación de un PaymentService, seleccionar entre EmailNotifier y SMSNotifier según los datos del cliente y cambiar la estrategia en tiempo de ejecución con seguridad.

¿Qué es y cuándo aplicar el strategy pattern en un servicio de pagos?

El patrón se usa cuando hay múltiples estrategias (clases, funciones o algoritmos) que resuelven el mismo problema. Una señal clara: un atributo está tipado como interfaz o protocolo, no como una implementación concreta. En este caso, el atributo es el notificador del PaymentService, tipado como Notifier Protocol, lo que permite intercambiar distintas implementaciones para un mismo objetivo.

  • Varias clases implementan una misma interfaz para resolver el mismo caso de uso.
  • Un atributo tipado como protocolo/interfaz indica que puedes aplicar el patrón.
  • En el contexto: el atributo notificador del PaymentService se tipa con Notifier Protocol.
  • La selección se basa en datos del cliente: email o teléfono en su contact info.
  • El cambio en tiempo de ejecución se realiza con un método dedicado: set_notifier.

¿Cómo implementar el cambio de estrategia en tiempo de ejecución en Python?

Primero se crea un método que permita modificar la implementación en tiempo de ejecución. Luego, una función elige la estrategia correcta según el caso de uso: si el cliente tiene teléfono, SMS; si tiene email, correo electrónico; si no hay ninguno, se lanza una excepción clara.

¿Cómo se define set_notifier?

from typing import Protocol

class NotifierProtocol(Protocol):
    def send(self, message: str) -> None: ...

class PaymentService:
    def __init__(self, notifier: NotifierProtocol) -> None:
        self.notifier = notifier

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

¿Qué hace get_notifier_implementation(customer_data)?

# Imports de dominio (ilustrativos):
# from commons import CustomerData, ContactInfo
# from notifiers import EmailNotifier, SMSNotifier

sms_gateway = None  # placeholder para el *gateway* de SMS.

def get_notifier_implementation(customer_data) -> NotifierProtocol:
    if customer_data.contact_info.phone:
        return SMSNotifier(gateway=sms_gateway)
    if customer_data.contact_info.email:
        return EmailNotifier()
    raise Exception("No se puede elegir la estrategia correcta")

Para probar la selección, puede definirse un generador de datos de ejemplo que devuelva un objeto con nombre y contact info con email o teléfono.

def get_customer_data():
    contact_info = ContactInfo(email="john.doe@mail.com", phone=None)
    return CustomerData(name="John Doe", contact_info=contact_info)

customer_data = get_customer_data()
notifier = get_notifier_implementation(customer_data)
service = PaymentService(notifier=notifier)

¿Cómo seleccionar y conmutar entre email y SMS según el contexto?

Es útil encapsular la creación de notifiers en funciones pequeñas y reutilizables. Así, si el envío falla, puedes conmutar la estrategia con rapidez usando set_notifier. Si ninguna condición aplica, la función de selección lanza una excepción, lo que también sugiere pensar en una tercera estrategia.

¿Cómo preparar notifiers reutilizables?

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

def get_sms_notifier() -> SMSNotifier:
    return SMSNotifier(gateway=sms_gateway)

email_notifier = get_email_notifier()
sms_notifier = get_sms_notifier()

¿Cómo cambiar de estrategia durante el procesamiento?

service = PaymentService(notifier=notifier)

try:
    # Procesar pago y notificar con la estrategia actual.
    pass
except Exception:
    print("Cambio de estrategia de notificación a estrategia de email")
    service.set_notifier(email_notifier)
    try:
        # Reintentar notificación con email.
        pass
    except Exception:
        service.set_notifier(sms_notifier)
        # Reintentar notificación con SMS.
  • Strategy Pattern: múltiples implementaciones intercambiables para el mismo problema.
  • Notifier Protocol: el atributo notificador se tipa como protocolo para desacoplar.
  • Selección de estrategia: get_notifier_implementation elige entre EmailNotifier y SMSNotifier según contact info.
  • Cambio en tiempo de ejecución: set_notifier permite sustituir la estrategia tras una excepción.
  • Excepción explícita: si no hay email ni teléfono, se lanza "No se puede elegir la estrategia correcta".
  • CustomerData y ContactInfo: contienen email y/o phone, base de la decisión.
  • EmailNotifier y SMSNotifier: estrategias concretas; SMS puede requerir un gateway.
  • Procesador de pagos: se puede aplicar el mismo patrón para seleccionar el processor según el tipo de pago.

Te leo en comentarios: ¿cómo aplicarías el patrón de estrategia para elegir el procesador de pagos según el tipo de pago que recibes?