No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Implementando el Patrón Strategy

16/27
Recursos

¿Cómo se implementa el patrón de estrategia en un servicio de pagos de Python?

En este fascinante recorrido por el patrón de diseño "Strategy", nos enfocamos en su aplicación a un servicio de pagos en Python. Este patrón es ideal cuando estamos frente a múltiples soluciones que abordan un mismo tipo de problema, permitiéndonos cambiar las estrategias en tiempo de ejecución. En este caso, ayudaremos a entender cómo utilizar un atributo "notificador" dentro de una clase de "PaymentService" utilizando el patrón de estrategia.

¿Cómo modificar la estrategia en tiempo de ejecución?

Uno de los pasos esenciales para implementar el patrón de estrategia es crear un método que permita modificar la implementación de la estrategia durante la ejecución del programa. Aquí se define un método setNotifier dentro del servicio de pagos, donde:

  • El parámetro de este método es un "notificador" del tipo "Notifier protocol".
  • Simplemente se cambia el atributo del notificador dentro del objeto.
  • Se utiliza un print para indicar que la implementación del notificador ha cambiado.
def setNotifier(self, notifier: NotifierProtocol):
    self.notifier = notifier
    print("Cambiando la implementación del notificador.")

¿Cómo elegir la estrategia correcta?

Es vital tener una función que seleccione la estrategia correcta basándose en las condiciones del problema. En este caso, se crea getNotifierImplementation que:

  • Recibe como parámetro datos de un cliente.
  • Evalúa si los datos de contacto del cliente contienen un teléfono o un correo electrónico.
  • Selecciona la estrategia adecuada (envío por SMS o email) según la información proporcionada.
def getNotifierImplementation(customer_data: CustomerData) -> NotifierProtocol:
    if customer_data.contact_info.phone:
        return SMSNotifier(SMSGetwayMock())
    elif customer_data.contact_info.email:
        return EmailNotifier()
    else:
        raise ValueError("No se puede elegir la estrategia correcta.")

¿Cómo se define una función para manejar los datos del cliente?

Para lograr una correcta elección de estrategia, es esencial tener datos del cliente bien estructurados. Se recomienda definir una función get_customer_data que prepare estos datos:

  • Retorna un objeto CustomerData con información relevante como el nombre y el email.
  • Se utiliza para poblar los parámetros de getNotifierImplementation.
def get_customer_data() -> CustomerData:
    contact_info = ContactInfo(email="[email protected]")
    return CustomerData(name="John Doe", contact_info=contact_info)

¿Qué hacer si falla la estrategia seleccionada?

El cambio de estrategias en tiempo de ejecución permite adaptarse a situaciones inesperadas, como el fallo en el envío del correo. Un ejemplo de cómo podría ser gestionado es mediante:

  • Crear funciones específicas para obtener instancias de notificaciones de email o SMS.
  • Cambiar la estrategia utilizando service.setNotifier en caso de error con la estrategia actual.
def get_email_notifier() -> EmailNotifier:
    return EmailNotifier()

def get_sms_notifier() -> SMSNotifier:
    return SMSNotifier(SMSGetwayMock())

# Cambio manual de estrategia
try:
    # Ejecutar envío con notificador actual...
except:
    service.setNotifier(get_email_notifier())

¿Existen otros casos de uso del patrón de estrategia?

El patrón de estrategia no está limitado a servicios de notificación. Por ejemplo, también es posible aplicarlo en la selección de un procesador de pagos basado en el tipo de pago que se está manejando. Esta versatilidad convierte el patrón de estrategia en una herramienta poderosa para crear aplicaciones flexibles y extensibles en Python.

El camino por el mundo de los patrones de diseño es emocionante y promete grandes beneficios a quienes los dominan. La adopción del patrón de estrategia en proyectos personales o profesionales puede representar una mejora significativa en la capacidad de adaptación y gestión de código. Te invitamos a seguir explorando y experimentando con diferentes implementaciones para llevar tus habilidades a un nuevo nivel de excelencia.

Aportes 5

Preguntas 4

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

El \*\*Patrón Strategy\*\* es ideal para escenarios donde necesitas implementar varias variantes de un algoritmo o comportamiento y quieres que sean intercambiables de manera dinámica sin alterar el código del cliente. Este patrón encapsula cada algoritmo dentro de su propia clase y permite al cliente seleccionar la estrategia que desea usar en tiempo de ejecución. \### Implementación del Patrón Strategy en Python A continuación, implementaremos un ejemplo práctico: un sistema para calcular descuentos en una tienda. Según el tipo de cliente, aplicaremos diferentes estrategias de descuento. \#### Paso 1: Crear una Interfaz para las Estrategias Definimos una clase base que todas las estrategias implementarán. ```python from abc import ABC, abstractmethod class DiscountStrategy(ABC): @abstractmethod def calculate(self, amount: float) -> float: """Calcula el descuento aplicado a la cantidad dada.""" pass ``` \#### Paso 2: Implementar Estrategias Concretas Creamos varias clases que representan diferentes estrategias de descuento. ```python class NoDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return amount # Sin descuento class SeasonalDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return amount \* 0.9 # 10% de descuento class LoyaltyDiscount(DiscountStrategy): def calculate(self, amount: float) -> float: return amount \* 0.85 # 15% de descuento ``` \#### Paso 3: Crear la Clase Contexto El contexto es la clase que utiliza las estrategias de descuento. Permite establecer y cambiar dinámicamente la estrategia de descuento. ```python class ShoppingCart: def \_\_init\_\_(self): self.total\_amount = 0 self.discount\_strategy: DiscountStrategy = NoDiscount() def add\_item(self, price: float): self.total\_amount += price def set\_discount\_strategy(self, strategy: DiscountStrategy): self.discount\_strategy = strategy def checkout(self) -> float: return self.discount\_strategy.calculate(self.total\_amount) ``` \- \*\*`add\_item`\*\*: Añade el precio de un artículo al carrito. \- \*\*`set\_discount\_strategy`\*\*: Permite configurar la estrategia de descuento deseada. \- \*\*`checkout`\*\*: Calcula el total con el descuento aplicado. \#### Paso 4: Usar el Patrón Strategy Ahora, podemos usar el sistema con diferentes estrategias de descuento. ```python \# Crear un carrito de compras cart = ShoppingCart() \# Agregar artículos al carrito cart.add\_item(100) cart.add\_item(200) \# Sin descuento print(f"Total sin descuento: {cart.checkout()}") # Output: 300.0 \# Aplicar un descuento de temporada cart.set\_discount\_strategy(SeasonalDiscount()) print(f"Total con descuento de temporada: {cart.checkout()}") # Output: 270.0 \# Cambiar a descuento por fidelidad cart.set\_discount\_strategy(LoyaltyDiscount()) print(f"Total con descuento por fidelidad: {cart.checkout()}") # Output: 255.0 ``` \### Ventajas del Patrón Strategy 1\. \*\*Cambio Dinámico de Comportamiento\*\*: Es fácil cambiar el algoritmo utilizado en tiempo de ejecución. 2\. \*\*Separación de Responsabilidades\*\*: Cada estrategia está encapsulada en su propia clase, lo que hace el código más modular y fácil de mantener. 3\. \*\*Cumplimiento del Principio Abierto/Cerrado (OCP)\*\*: Podemos añadir nuevas estrategias sin modificar el código existente. \### Desventajas del Patrón Strategy 1\. \*\*Aumento del Número de Clases\*\*: Cada nueva estrategia requiere una clase concreta, lo que puede aumentar la complejidad en proyectos grandes. 2\. \*\*Complejidad Inicial\*\*: Puede ser excesivo para problemas simples donde no se necesitan múltiples estrategias. \### Conclusión El \*\*Patrón Strategy\*\* es una solución elegante para problemas donde necesitas encapsular algoritmos intercambiables. Esta implementación en Python demuestra cómo el patrón puede mejorar la flexibilidad y extensibilidad del sistema al permitir cambiar dinámicamente los comportamientos sin alterar la lógica central.
No se agregaron los recursos en esta clase que es de implementación, pero si en la de explicación. Sería genial que lo corrigieran, así poder ir avanzando de una mejor manera.
Tuve muchos problemas con este código ;-; Sin exagerar, pasé un día solo haciendo que corra, sin resolver los ejercicios. Y quise dejar algunos problemas con el código actual: 1- ve a la carpeta `src/payment_service/listeners`, dirígete al archivo \_\_init\_\_.py y agrega la linea `from listener import Listener` , también agrega a la lista "`listener`" 2- Cuando se instancia el service (al final del archivo main.py), en las validaciones también me salía un error, y por ello adjunto esto: ```python if __name__ == "__main__": stripe_pay_processor = StripePaymentProcessor() # 🆗 principio S customer_data =get_customer_info() notifier= get_notifier_implementation(customer_data = customer_data) # Para cambiar de estrategia según el contexto parte 1: email_notifier = get_mail sms_notifier = get_sms() # Se crea el manejador de dict y configura la cadena de validación handler_map = CustomerHandler() payment_data_validator = PaymentDataValidator() handler_map.set_next(payment_data_validator) # Asigna la cadena de validadores a dictionaries_map dictionaries_map = handler_map logger = TransactionLogger() service = PaymentService( payment_processor=stripe_pay_processor, notifier=notifier, #🆗 validators= dictionaries_map, logger=logger ) # parte 2: Cambia la estrategia a email: 🆗 service.set_notifier(EmailNotifier) #si falla: escoge la otra estrategia: service. set_notifier() ```Ojalá les sea de ayuda. búscame como @camilocsoto🐈‍⬛
Buenas! Alguien me explica porqué en la función "get\_notifier\_implementation()" directamente no retorno la instancia y debo crear una función a parte como "get\_email\_notifier()" o "get\_sms\_notifier()" Gracias!
Este es mi solucion al desafio del profesor para implementar el patron strategy en el payment\_procesor. Si alguien nota algo mal o a mejorar me avisa. from enum import Enum ```python from enum import Enum from notifiers import EmailNotifier, NotifierProtocol, SMSNotifier from .commons import CustomerData, ContactInfo from .processors import StripePaymentProcessor, PaymentProcessorProtocol, LocalPaymentProcessor, OfflinePaymentProcessor from .service import PaymentService from .validators import CustomerValidator, PaymentDataValidator from .loggers import TransactionLogger def get_email_notifier() -> EmailNotifier: return EmailNotifier() def get_sms_notifier() -> SMSNotifier: return SMSNotifier(gateway="SMSGateway") def get_stripe_processor() -> StripePaymentProcessor: return StripePaymentProcessor() def get_local_processor() -> LocalPaymentProcessor: return LocalPaymentProcessor() def get_offline_processor() -> OfflinePaymentProcessor: return OfflinePaymentProcessor() def get_notifier_implementation( customer_data: CustomerData, ) -> NotifierProtocol: if customer_data.contact_info.phone: return get_sms_notifier() if customer_data.contact_info.email: return get_email_notifier() raise ValueError("No se puede elegir la estrategia correcta") def get_customer_data() -> CustomerData: contact_info = ContactInfo(email="[email protected]", phone="+1234567890") customer_data = CustomerData( name="John Doe", contact_info=contact_info, ) return customer_data class PaymentProcessorType(Enum): STRIPE ="Stripe" LOCAL = "Local" OFFLINE = "Offline" def get_payment_processor_implementation( payment_processor_type: PaymentProcessorType, ) -> PaymentProcessorProtocol: if PaymentProcessorType.STRIPE == payment_processor_type: return get_stripe_processor() if PaymentProcessorType.LOCAL == payment_processor_type: return get_local_processor() if PaymentProcessorType.OFFLINE == payment_processor_type: return get_offline_processor() raise ValueError("No se puede elegir la estrategia correcta") def get_payment_processor_type() -> PaymentProcessorType: payment_processor_type = PaymentProcessorType.STRIPE return payment_processor_type if __name__ == "__main__": payment_processor = get_payment_processor_implementation(get_payment_processor_type()) strip_processor = get_stripe_processor() local_processor = get_local_processor() offline_processor = get_offline_processor() notifier = get_notifier_implementation(get_customer_data()) email_notifier = get_email_notifier() sms_notifier = get_sms_notifier() costumer_validator = CustomerValidator() payment_data_validator = PaymentDataValidator() logger = TransactionLogger() service = PaymentService( payment_processor=payment_processor, notifier=notifier, customer_validator=costumer_validator, payment_validator=payment_data_validator, logger=logger, ) # Cambio de estrategia de notificación a estrategia de email service.set_notifier(email_notifier) # Cambio de estrategia de notificación a estrategia de SMS service.set_notifier(sms_notifier) # Cambio de estrategia de procesamiento de pago a estrategia de Stripe service.set_payment_processor(strip_processor) # Cambio de estrategia de procesamiento de pago a estrategia de Local service.set_payment_processor(local_processor) # Cambio de estrategia de procesamiento de pago a estrategia de Offline service.set_payment_processor(offline_processor) ```