You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesi贸n a prueba de IA

Antes: $249

Currency
$209
Suscr铆bete

Termina en:

1 D铆as
11 Hrs
6 Min
41 Seg

Aplicar el Principio de Segregaci贸n de Interfaces (ISP)

10/27
Resources

Implementing the principle of interface segregation is key to keeping the code clean and flexible in complex systems such as a payment processor. In this case, several enhancements were addressed within the payment processor including the creation of specific methods for refunds and recurrences, along with the proper segregation of interfaces according to the capabilities of each payment processor.

What changes were made to the payment processor?

  • Two new methods were added: refund and recurrence creation.
  • A second payment processor, the offline processor, which simulates cash payments, was implemented. However, this processor cannot perform refunds or create recurrences.
  • The processTransaction method was modified to not depend on the Stripe attribute, as there are now multiple processors.

Why did the implementation of the interface segregation principle fail?

The offline payment processor implemented methods that it could not use, such as refunds and recurrences, which violated the interface segregation principle. This principle states that a class should not depend on methods that it cannot implement or use.

How was the problem corrected?

  • Two new protocols were created: one for refunds(RefundPaymentProtocol) and one for recurrences(RecurringPaymentProtocol).
  • These protocols exclusively define the methods for these actions, eliminating the need for processors such as offline to implement methods they do not need.
  • Processors that can perform all actions, such as Stripe, now implement all three protocols: one for collections, one for refunds, and one for recurrences.

What other adjustments were made to the service?

  • Optional attributes were added for the refund and recurring processors(RefundProcessor and RecurringProcessor), allowing each type of processor to handle only the actions that correspond to it.
  • Validations were implemented to avoid exceptions in case a processor does not support certain actions. If the processor does not support refunds or recurrences, an exception is thrown with a clear message.

How does this change affect the Stripe processor and the offline processor?

  • In the case of Stripe, it now handles all three protocols, allowing you to collect, make refunds and handle recurring payments.
  • The offline processor, by not handling refunds and recurrences, no longer has to implement those methods, complying with the principle of interface segregation.

Contributions 9

Questions 2

Sort by:

Want to see more contributions, questions and answers from the community?

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.
Hola :D Recre茅 el archivo **before.py** para que puedas usarlo: ```python """ features agregados: + PaymentResponse: objeto + RefundPaymentProtocol: interfaz + PaymentProcessorProtocol:interfaz + RecurringPaymentProtocol: interfaz + StripePaymentProcessor: subclase (ejecuta demasiadas cosas) + OfflinePaymentProcessor: subclase ------------------------------------------------------------- PREGUNTA: 驴Qu茅 principio SOLID se estaba siguiendo en los m茅todos de recurrencia? El principio S: single responsibity principle porque est谩 dedicado 煤ni- camente a configurar los pagos con stripe y de la suscripci贸n est谩n delegadas a m茅todos auxiliares """ import os from dataclasses import dataclass, field from typing import Optional, Protocol import uuid import stripe from dotenv import load_dotenv from pydantic import BaseModel from stripe.error import StripeError _ = load_dotenv() class ContactInfo(BaseModel): email: Optional[str] = None phone: Optional[str] = None class CustomerData(BaseModel): name: str contact_info: ContactInfo customer_id: Optional[str] = None class PaymentData(BaseModel): amount: int source: str class PaymentResponse(BaseModel): status: str amount: int transaction_id: Optional[str] = None message: Optional[str] = None class PaymentProcessor(Protocol): """ Protocol for processing payments, refunds, and recurring payments. # HEREDA A TODOS LOS PAYMENTPROCESOR This protocol defines the interface for payment processors. Implementations should provide methods for processing payments, refunds, and setting up recurring payments. """ def process_transaction( self, customer_data: CustomerData, payment_data: PaymentData ) -> PaymentResponse: ... def refund_payment(self, transaction_id:str) -> PaymentResponse: ... def setup_recurring_payment( self, customer_data: CustomerData, payment_data: PaymentData ) ->PaymentResponse: ... class StripePaymentProcessor(PaymentProcessor): def process_transaction( self, customer_data: CustomerData, payment_data: PaymentData ) -> PaymentResponse: stripe.api_key = os.getenv("STRIPE_API_KEY") try: charge = stripe.Charge.create( amount=payment_data.amount, currency="usd", source=payment_data.source, description="Charge for " + customer_data.name, ) print("Payment successful") return PaymentResponse( status=charge["status"], amount=charge["amount"], transaction_id=charge["id"], message="Payment successful", ) except StripeError as e: print("Payment failed:", e) return PaymentResponse( status="failed", amount=payment_data.amount, transaction_id=None, message=str(e), ) def refund_payment(self, transaction_id: str) -> PaymentResponse: stripe.api_key = os.getenv("STRIPE_API_KEY") try: refund = stripe.Refund.create(charge=transaction_id) print("Refund successful") return PaymentResponse( status=refund["status"], amount=refund["amount"], transaction_id=refund["id"], message="Refund successful", ) except StripeError as e: print("Refund failed:", e) return PaymentResponse( status="failed", amount=0, transaction_id=None, message=str(e), ) def setup_recurring_payment( self, customer_data: CustomerData, payment_data: PaymentData ) -> PaymentResponse: stripe.api_key = os.getenv("STRIPE_API_KEY") price_id = os.getenv("STRIPE_PRICE_ID", "") try: customer = self._get_or_create_customer(customer_data) payment_method = self._attach_payment_method( customer.id, payment_data.source ) self._set_default_payment_method(customer.id, payment_method.id) subscription = stripe.Subscription.create( customer=customer.id, items=[ {"price": price_id}, ], expand=["latest_invoice.payment_intent"], ) print("Recurring payment setup successful") amount = subscription["items"]["data"][0]["price"]["unit_amount"] return PaymentResponse( status=subscription["status"], amount=amount, transaction_id=subscription["id"], message="Recurring payment setup successful", ) except StripeError as e: print("Recurring payment setup failed:", e) return PaymentResponse( status="failed", amount=0, transaction_id=None, message=str(e), ) def _get_or_create_customer( self, customer_data: CustomerData ) -> stripe.Customer: """ Creates a new customer in Stripe or retrieves an existing one. """ if customer_data.customer_id: customer = stripe.Customer.retrieve(customer_data.customer_id) print(f"Customer retrieved: {customer.id}") else: if not customer_data.contact_info.email: raise ValueError("Email required for subscriptions") customer = stripe.Customer.create( name=customer_data.name, email=customer_data.contact_info.email ) print(f"Customer created: {customer.id}") return customer def _attach_payment_method( self, customer_id: str, payment_source: str ) -> stripe.PaymentMethod: """ Attaches a payment method to a customer. """ payment_method = stripe.PaymentMethod.retrieve(payment_source) stripe.PaymentMethod.attach( payment_method.id, customer=customer_id, ) print( f"Payment method {payment_method.id} attached to customer {customer_id}" ) return payment_method def _set_default_payment_method( self, customer_id: str, payment_method_id: str ) -> None: """ Sets the default payment method for a customer. """ stripe.Customer.modify( customer_id, invoice_settings={ "default_payment_method": payment_method_id, }, ) print(f"Default payment method set for customer {customer_id}") class OfflinePaymentProcessor(PaymentProcessor): def process_transaction( self, customer_data: CustomerData, payment_data: PaymentData ) -> PaymentResponse: print("Processing offline payment for", customer_data.name) return PaymentResponse( status="success", amount=payment_data.amount, transaction_id=str(uuid.uuid4()), message="Offline payment success", ) """ los siguientes m茅todos levantan errores porque no se pueden hacer reembolsos ni recurrencias a efectivo se incumple el principio de segregaci贸n de interfaces porque una clase no deber铆a depender de clases que no puede implementar """ def refund_payment(self, transaction_id: str) -> PaymentResponse: print("refunds aren't supported in OfflinePaymentOricessor.") raise NotImplementedError("Refunds not supported in offline processor.") def setup_recurring_payment(self, customer_data: CustomerData, payment_data: PaymentData ) ->PaymentResponse: print("recurring payments aren't supported in OfflinePaymentOricessor.") raise NotImplementedError("Refunds not supported in offline processor.") class Notifier(Protocol): """ Protocol for sending notifications. This protocol defines the interface for notifiers. Implementations should provide a method `send_confirmation` that sends a confirmation to the customer. """ def send_confirmation(self, customer_data: CustomerData): ... class EmailNotifier: def send_confirmation(self, customer_data: CustomerData): from email.mime.text import MIMEText if not customer_data.contact_info.email: raise ValueError("Email address is requiered to send an email") msg = MIMEText("Thank you for your payment.") msg["Subject"] = "Payment Confirmation" msg["From"] = "[email protected]" msg["To"] = customer_data.contact_info.email print("Email sent to", customer_data.contact_info.email) @dataclass class SMSNotifier: gateway: str def send_confirmation(self, customer_data: CustomerData): phone_number = customer_data.contact_info.phone if not phone_number: print("No phone number provided") return print( f"SMS sent to {phone_number} via {self.gateway}: Thank you for your payment." ) class TransactionLogger: def log_transaction( self, customer_data: CustomerData, payment_data: PaymentData, payment_response: PaymentResponse, ): with open("transactions.log", "a") as log_file: log_file.write( f"{customer_data.name} paid {payment_data.amount}\n" ) log_file.write(f"Payment status: {payment_response.status}\n") if payment_response.transaction_id: log_file.write( f"Transaction ID: {payment_response.transaction_id}\n" ) log_file.write(f"Message: {payment_response.message}\n") def log_refund( self, transaction_id: str, refund_response: PaymentResponse ): with open("transactions.log", "a") as log_file: log_file.write( f"Refund processed for transaction {transaction_id}\n" ) log_file.write(f"Refund status: {refund_response.status}\n") log_file.write(f"Message: {refund_response.message}\n") class CustomerValidator: def validate(self, customer_data: CustomerData): if not customer_data.name: print("Invalid customer data: missing name") raise ValueError("Invalid customer data: missing name") if not customer_data.contact_info: print("Invalid customer data: missing contact info") raise ValueError("Invalid customer data: missing contact info") if not ( customer_data.contact_info.email or customer_data.contact_info.phone ): print("Invalid customer data: missing email and phone") raise ValueError("Invalid customer data: missing email and phone") class PaymentDataValidator: def validate(self, payment_data: PaymentData): if not payment_data.source: print("Invalid payment data: missing source") raise ValueError("Invalid payment data: missing source") if payment_data.amount <= 0: print("Invalid payment data: amount must be positive") raise ValueError("Invalid payment data: amount must be positive") @dataclass class PaymentService: payment_processor: PaymentProcessor notifier: Notifier customer_validator: CustomerValidator = field( default_factory=CustomerValidator ) payment_validator: PaymentDataValidator = field( default_factory=PaymentDataValidator ) logger: TransactionLogger = field(default_factory=TransactionLogger) def process_transaction( self, customer_data: CustomerData, payment_data: PaymentData ) -> PaymentResponse: self.customer_validator.validate(customer_data) self.payment_validator.validate(payment_data) payment_response = self.payment_processor.process_transaction( customer_data, payment_data ) self.notifier.send_confirmation(customer_data) self.logger.log_transaction( customer_data, payment_data, payment_response ) return payment_response def process_refund(self, transaction_id: str): refund_response = self.payment_processor.refund_payment(transaction_id) self.logger.log_refund(transaction_id, refund_response) return refund_response def setup_recurring( self, customer_data: CustomerData, payment_data: PaymentData ): recurring_response = self.payment_processor.setup_recurring_payment( customer_data, payment_data ) self.logger.log_transaction( customer_data, payment_data, recurring_response ) return recurring_response if __name__ == "__main__": # Set up the payment processors stripe_processor = StripePaymentProcessor() offline_processor = OfflinePaymentProcessor() # Set up the customer data and payment data customer_data_with_email = CustomerData( name="John Doe", contact_info=ContactInfo(email="[email protected]") ) customer_data_with_phone = CustomerData( name="Jane Doe", contact_info=ContactInfo(phone="1234567890") ) # Set up the payment data payment_data = PaymentData(amount=100, source="tok_visa") # Set up the notifiers email_notifier = EmailNotifier() sms_gateway = "YourSMSService" sms_notifier = SMSNotifier(sms_gateway) # # Using Stripe processor with email notifier payment_service_email = PaymentService(stripe_processor, email_notifier) payment_service_email.process_transaction(customer_data_with_email, payment_data) # Using Stripe processor with SMS notifier payment_service_sms = PaymentService(stripe_processor, sms_notifier) sms_payment_response = payment_service_sms.process_transaction(customer_data_with_phone, payment_data) #Using strupe processor with SMS notifier payment_service_sms = PaymentService(stripe_processor, sms_notifier) sms_payment_response = payment_service_sms.process_transaction(customer_data_with_phone, payment_data) # Example of processing a refund using Stripe processor transaction_id_to_refund = sms_payment_response.transaction_id if transaction_id_to_refund: payment_service_email.process_refund(transaction_id_to_refund) # Using offline processor with email notifier offline_payment_service = PaymentService(offline_processor, email_notifier) offline_payment_response = offline_payment_service.process_transaction( customer_data_with_email, payment_data ) # Attempt to refund using offline processor (will fail) try: if offline_payment_response.transaction_id: offline_payment_service.process_refund( offline_payment_response.transaction_id ) except Exception as e: print(f"Refund failed and PaymentService raised an exception: {e}") # Attempt to set up recurring payment using offline processor (will fail) try: offline_payment_service.setup_recurring( customer_data_with_email, payment_data ) except Exception as e: print( f"Recurring payment setup failed and PaymentService raised an exception {e}" ) try: error_payment_data = PaymentData(amount=100, source="tok_radarBlock") payment_service_email.process_transaction( customer_data_with_email, error_payment_data ) except Exception as e: print(f"Payment failed and PaymentService raised an exception: {e}") # Set up recurrence recurring_payment_data = PaymentData(amount=100, source="pm_card_visa") payment_service_email.setup_recurring( customer_data_with_email, recurring_payment_data ) ```
Los archivos `before.py` y `after.py` son exactamente el mismo. Por favor, actualizarlos.
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.
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
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.
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() tipo: refund=RefundPaymentProtocol() y esta se entrega como parametro "refund\_proccesor=refund" Espero haberme dado a entender
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. ```python class Device: def turn\_on(self): raise NotImplementedError def turn\_off(self): raise NotImplementedError def play\_music(self): raise NotImplementedError class Speaker(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...") class Lamp(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. ```python from abc import ABC, abstractmethod \# Interfaces espec铆ficas class PowerDevice(ABC): @abstractmethod def turn\_on(self): pass @abstractmethod def turn\_off(self): pass class MusicPlayer(ABC): @abstractmethod def play\_music(self): pass \# Clases que implementan las interfaces relevantes class Speaker(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...") class Lamp(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. ```python \# 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.