Aplicar el principio de segregación de interfaces en un procesador de pagos evita dependencias innecesarias y errores en tiempo de ejecución. Aquí verás cómo separar responsabilidades en protocolos específicos para cobros, reembolsos y recurrencias, con ejemplos prácticos usando Stripe y un procesador offline. La meta: clases que solo dependan de lo que realmente usan.
¿Cómo aplicar el principio de segregación de interfaces en un procesador de pagos?
El punto de partida es un procesador que ahora incluye dos métodos nuevos: reembolso y crear recurrencia. Además, se añadió un procesador de pagos offline que solo imprime el cobro, simulando efectivo. Al no tener pasarela, no puede reembolsar ni crear recurrencias, por lo que lanzar excepciones en esos métodos revela la violación al principio: una clase no debería depender de métodos que no puede implementar.
Se cambia el retorno de process transaction para no depender de un charge de Stripe. Ahora se define un resultado con: estatus exitoso o fallido, monto, ID de transacción y mensaje de la pasarela.
El procesador offline no soporta refund ni recurring: levantar excepciones lo hace evidente y guía la refactorización.
¿Qué problema revela el procesador de pagos offline?
Cuando el offline processor implementa métodos de reembolso y recurrencia que no puede cumplir, se obliga a los clientes a depender de funciones inútiles. Eso contraviene el principio de segregación de interfaces y dificulta el mantenimiento.
¿Qué protocolos separar: cobro, reembolso y recurrencia?
La solución es crear tres protocolos con un único propósito cada uno: uno para cobrar, otro para reembolsar y otro para recurrencia. Así, cada clase implementa solo lo que necesita.
from typing import Protocol, Optional
classPMProcessorProtocol(Protocol):defprocess_transaction(self, amount:int)->dict:...classRefundPaymentProtocol(Protocol):defrefund_payment(self, transaction_id:str)->None:...classRecurringPaymentProtocol(Protocol):defcreate_recurring(self, amount:int, interval:str)->None:...
¿Qué cambios de diseño y código se realizan para Stripe y offline?
En Stripe, se implementan los tres métodos porque la pasarela soporta cobros, reembolsos y recurrencias. El procesador offline solo implementa cobros.
Stripe: hereda de los tres protocolos. Soporta todo el flujo.
Offline: hereda solo de PMProcessorProtocol. No tiene reembolso ni recurrencia.
classStripeProcessor(PMProcessorProtocol, RefundPaymentProtocol, RecurringPaymentProtocol):defprocess_transaction(self, amount:int)->dict:# lógica real de Stripereturn{"status":"success","amount": amount}defrefund_payment(self, transaction_id:str)->None:# refund real en Stripe...defcreate_recurring(self, amount:int, interval:str)->None:# plan/recurrencia en Stripe...classOfflineProcessor(PMProcessorProtocol):defprocess_transaction(self, amount:int)->dict:print(f"Cobrando en efectivo: {amount}")return{"status":"success","amount": amount}
Nota: se menciona renombrar la clase base para cumplir un estándar: PM Processor Protocol. El objetivo es claridad y consistencia en nombres.
¿Cómo adaptar el servicio para refund y recurrencia opcionales?
Como los métodos de refund y recurring ya no están en el payment processor general, el service agrega dos atributos opcionales: refund_processor y recurring_processor. Se valida None antes de llamar y, de no existir, se lanza una excepción clara.
Agregar atributos opcionales: Optional[RefundPaymentProtocol] y Optional[RecurringPaymentProtocol].
Validar antes de ejecutar: si es None, lanzar Exception con mensajes explícitos.
Instanciación flexible: con Stripe, un solo procesador puede cubrir los tres flujos; con offline, no se pasan refund ni recurring.
classPaymentService:def__init__( self, payment_processor: PMProcessorProtocol, refund_processor: Optional[RefundPaymentProtocol]=None, recurring_processor: Optional[RecurringPaymentProtocol]=None,)->None: self.payment_processor = payment_processor
self.refund_processor = refund_processor
self.recurring_processor = recurring_processor
defrefund(self, transaction_id:str)->None:ifnot self.refund_processor:raise Exception("this processor does not support refunds") self.refund_processor.refund_payment(transaction_id)defcreate_recurring(self, amount:int, interval:str)->None:ifnot self.recurring_processor:raise Exception("This processor does not support recurrent") self.recurring_processor.create_recurring(amount, interval)
Instanciación con Stripe como único procesador para todo:
Tipos opcionales y validación de None: robustez en el service.
Excepciones claras: mensajes orientados a capacidades del procesador.
Independencia de pasarela: retorno genérico en process transaction (estatus, monto, id, mensaje).
¿Qué otras mejoras aplicarías para reforzar la segregación de interfaces en este caso de uso de procesadores de pago? Comparte tus ideas y ejemplos en comentarios.
Segregación de interfaces en procesadores de pagos
Que los archivos after.py y before.py sean los mismos y sumado a los cambios que hace el profesor por fuera de las ediciones se hace muy dificil de poder seguir el curso. Por favor actualicen el curso para no tener que andar pausando y copiando el codigo a mano.
el curso no sigue una historia en el código. Arreglen las cosas, este curso es re interesante, pongan a alguien a chequear todo desde el inicio asi pueden ir corrigiendo todas las clases.
Aunque solo entre a este curso para aprender pues estaba en mi ruta de aprendizaje en mi vida e visto un procesador de pago asi que aunque no entiendo mucho me estoy documentando y he ido aprendiendo de solid + programacion y otras cosas como el procesador de pago, ciertamente no es tan facil pero a veces quedo en el aire con algunas explicaciones. Bien agradezco el curso si puedes dar una explicacion asi sea en la descripcion algo mas detallado de algunos terminos seria excelente de resto gracias.
Entiendo que ustedes fomentan que la comunidad se apoye cuando ocurra un inconveniente, pero tampoco es para que nosotros les hagamos el trabajo gratis. Claramente, me refiero al problema de que los archivos before y after desde hace tres clases son los mismos, y esto dificulta que algunos alumnos puedan seguir la clase.
el contenido de before y after es el mismo lo cual a mi me molesta un poco a la hora de querer practicar el refactorizado ya que no puedo tener el código que el modifico para la clase y debo pausar el video para copiar lo que el hizo
como hasta el minuto 1:48 se muestra el codigo sin segregacion de interfaces que deberia estar en el archivo de after.py donde vemos que el metodo OfflinePaymentProcessor esta obligado a tener metodos que no va usar. luego de alli empieza a explicar que los metodos RefunPayment y RecurrinPayment debe ser metodos abstractos para que puedan ser heredados unicamente donde se necesitan que en este caso seria en el metodo StripePaymentProcessor donde ya estarias usando la segregacion de interfaces.
El código no indica que la clase PaymentService tenga muchas responsabilidades. Lo que sí explica es cómo las dependencias se inyectan para cumplir con el principio de inversión de dependencias.
Profe Eduardo... Una duda.... al final de la clase usted comenta que podemos usar "stripe_proccesor" como "refund_processor" y también como "recurring_proccesor"
DADO QUE
el stripe proccesor implementa las 3 interfaces... pero no entiendo porque
se da como parametro tipo
"refund_proccesor=stripe processor"
y en vez de esto no se crea una instancia de RefundPaymentProtocol()
Entonces, ¿es recomendable usar las interfaces de los servicios como propiedades de un objeto, en vez de extender del propio Protocolo a la hora de crear la clase de dicho objeto?
Para aplicar el **Principio de Segregación de Interfaces** (ISP) en el diseño de código, es clave asegurarse de que las clases no estén obligadas a depender de métodos que no usan. Esto se logra dividiendo interfaces grandes en varias interfaces más pequeñas y específicas, de manera que cada clase solo implemente los métodos que necesita.
### Ejemplo Práctico: Aplicación del ISP en Python
Imaginemos un sistema para manejar dispositivos electrónicos. Inicialmente, podríamos diseñar una interfaz llamada Device con métodos que representen varias funciones que un dispositivo podría tener, como encender, apagar y reproducir música. Sin embargo, no todos los dispositivos tienen las mismas capacidades. Un parlante puede reproducir música, pero una lámpara no.
#### Ejemplo Incorrecto: Violación del ISP
A continuación, un diseño que viola el ISP, en el que todos los dispositivos deben implementar todos los métodos, incluso si algunos no son relevantes.
classDevice:  def turn\_on(self):  raise NotImplementedError  def turn\_off(self):  raise NotImplementedError  def play\_music(self):  raise NotImplementedErrorclassSpeaker(Device):  def turn\_on(self):  print("Speaker is now on.")  def turn\_off(self):  print("Speaker is now off.")  def play\_music(self):  print("Playing music...")classLamp(Device):  def turn\_on(self):  print("Lamp is now on.")  def turn\_off(self):  print("Lamp is now off.")  def play\_music(self):  raise NotImplementedError("Lamps cannot play music.")
En este diseño, la clase Lamp debe implementar el método play\_music, a pesar de que una lámpara no reproduce música. Esto viola el ISP, ya que Lamp depende de un método que no le corresponde.
### Solución Correcta: Aplicación del ISP
Para aplicar el ISP, dividimos la interfaz Device en interfaces más pequeñas y específicas. Cada dispositivo solo implementará los métodos que realmente necesita.
from abc import ABC, abstractmethod
\# Interfaces específicas
classPowerDevice(ABC):  @abstractmethod  def turn\_on(self):  pass  @abstractmethod  def turn\_off(self):  passclassMusicPlayer(ABC):  @abstractmethod  def play\_music(self):  pass
\# Clases que implementan las interfaces relevantes
classSpeaker(PowerDevice, MusicPlayer):  def turn\_on(self):  print("Speaker is now on.")  def turn\_off(self):  print("Speaker is now off.")  def play\_music(self):  print("Playing music...")classLamp(PowerDevice):  def turn\_on(self):  print("Lamp is now on.")  def turn\_off(self):  print("Lamp is now off.")
### Explicación
- **Interfaz PowerDevice**: Esta interfaz define los métodos turn\_on y turn\_off, comunes a dispositivos que se pueden encender y apagar.
- **Interfaz MusicPlayer**: Define solo el método play\_music, exclusivo para dispositivos que pueden reproducir música.
- **Clase Speaker**: Implementa tanto PowerDevice como MusicPlayer, ya que puede encenderse y reproducir música.
- **Clase Lamp**: Implementa solo PowerDevice, ya que solo necesita los métodos de encendido y apagado.
### Uso del Código
Al aplicar el ISP, podemos usar Speaker y Lamp sin preocuparnos por métodos innecesarios o errores derivados de métodos irrelevantes.
\# Crear instancias de dispositivos
speaker = Speaker()lamp = Lamp()
\# Usar métodos relevantes
speaker.turn\_on()# Speaker is now on.speaker.play\_music()# Playing music...speaker.turn\_off()# Speaker is now off.lamp.turn\_on()# Lamp is now on.lamp.turn\_off()# Lamp is now off.
### Ventajas de Aplicar el ISP
1. **Código Más Limpio y Específico**: Cada clase implementa solo los métodos que necesita, eliminando métodos innecesarios.
2. **Mayor Cohesión y Mantenibilidad**: Las interfaces pequeñas y específicas facilitan el mantenimiento y la extensión del código.
3. **Mejora en la Legibilidad**: El código es más fácil de entender, ya que cada clase y cada interfaz tienen una responsabilidad clara.
### Conclusión
Aplicar el **Principio de Segregación de Interfaces** hace que el código sea más modular, fácil de mantener y específico. En este ejemplo, hemos logrado un diseño en el que cada dispositivo electrónico implementa solo los métodos que realmente utiliza, cumpliendo con el ISP y optimizando el sistema para agregar nuevos dispositivos sin generar dependencias innecesarias.