No tienes acceso a esta clase

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

Aplicar el Principio de Sustitución de Liskov (LSP)

8/27
Recursos

El principio de sustitución de Liskov nos permite escribir código flexible y reutilizable, pero también requiere que todas las clases o protocolos cumplan con una firma coherente. En esta clase, hemos aplicado este principio en Python reemplazando las clases abstractas con protocolos y detectando un bug deliberado que rompe este principio. Vamos a analizar cómo lo resolvimos y cómo aseguramos que las clases de notificación sean intercambiables sin modificar el código base.

¿Cómo reemplazamos las clases abstractas por protocolos?

  • Se sustituyeron las clases abstractas por protocolos en Python.
  • Los protocolos actúan de manera similar a las interfaces en otros lenguajes de programación.
  • En este caso, el Notifier y el PaymentProcessor fueron convertidos en protocolos.
  • Los métodos dentro de los protocolos fueron documentados usando docstrings en formato NumPy para mejorar la claridad.

¿Cómo se introdujo y detectó el bug?

  • Se introdujo un bug a propósito al cambiar la clase SMSNotifier.
  • El bug hizo que el método SendConfirmation no cumpliera con la firma requerida, ya que estaba aceptando un parámetro adicional: SMSGateway.
  • Esto provocaba que no fuera intercambiable con la clase EmailNotifier, lo que viola el principio de sustitución de Liskov.
  • Para detectarlo, se utilizó el debugger y un análisis de la firma del método.

¿Qué desafíos presenta el principio de sustitución de Liskov?

  • Mantener la consistencia en las firmas de los métodos entre clases hijas y protocolos es crucial.
  • Es fundamental evitar introducir parámetros adicionales o cambiar las firmas de los métodos, ya que esto rompe la intercambiabilidad de las clases.

Aportes 8

Preguntas 2

Ordenar por:

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

Hacía rato que no venía por los pagos de Platzi a hacer cursos! Da placer ver cursos de esta calidad en el producto! Mis sinceras felicitaciones a todo el Platzi Team!
El siguiente es un ejemplo sencillo en Python que muestra la implementación de interfaces utilizando Protocol: ```python from typing import Protocol class Notifier(Protocol): def send_notification(self, message: str) -> None: ... class EmailNotifier: def send_notification(self, message: str) -> None: print(f"Enviando correo: {message}") class SMSNotifier: def send_notification(self, message: str) -> None: print(f"Enviando SMS: {message}") def notify(notifier: Notifier, message: str) -> None: notifier.send_notification(message) email_notifier = EmailNotifier() sms_notifier = SMSNotifier() notify(email_notifier, "Hola a todos!") notify(sms_notifier, "Hola a todos!") ``` En este ejemplo, `Notifier` es un protocolo que define un método `send_notification`. Tanto `EmailNotifier` como `SMSNotifier` implementan este método, permitiendo usar cualquiera de los dos como notificadores.
Al invocar el método process\_transaction() de payment\_service\_sms\_notifier lo hace con el parámetro customer\_data\_with\_email y al invocar el de payment\_service ingresa el parámetro customer\_data\_with\_phone. No debería ser al revés? Debería fallar al tratar de buscar los atributos inexistente.
El formato de los Docstring no es Numpy, sino Sphinx.
Para aplicar el \*\*Principio de Sustitución de Liskov\*\* (LSP), las subclases deben comportarse de manera que puedan sustituir a su clase base sin alterar el funcionamiento del sistema. Esto implica que la subclase debe cumplir con el contrato de la clase base en cuanto a comportamiento, sin añadir restricciones o modificar expectativas fundamentales. \### Ejemplo: Aplicación Correcta del LSP en Python Imaginemos que estamos desarrollando un sistema para un zoológico que maneja diferentes tipos de aves. Tenemos una clase `Bird` que representa un ave general con un método `fly`. Queremos agregar subclases `Eagle` (que puede volar) y `Penguin` (que no vuela). \#### Ejemplo Incorrecto: Violación del LSP En este caso, al intentar hacer que `Penguin` herede de `Bird` pero no pueda volar, estamos violando el LSP, ya que `Penguin` no cumple con el comportamiento esperado (poder volar) de `Bird`. ```python class Bird: def fly(self): print("I can fly!") class Eagle(Bird): pass class Penguin(Bird): def fly(self): raise Exception("I can't fly!") ``` Con este diseño, `Penguin` no se comporta como un `Bird` común, ya que lanza una excepción en lugar de volar, lo cual rompe el LSP. Si un programa intenta llamar al método `fly` en una instancia de `Penguin` y espera que funcione igual que en `Bird`, se generará un error inesperado. \### Solución: Aplicando el LSP Para cumplir con el LSP, podemos rediseñar la estructura usando una clase base `Bird` con dos subclases específicas: `FlyingBird` y `NonFlyingBird`. Esto nos permite representar correctamente tanto a las aves que vuelan como a las que no, sin que una interfiera en la funcionalidad de la otra. ```python from abc import ABC, abstractmethod \# Clase base abstracta class Bird(ABC): @abstractmethod def make\_sound(self): pass \# Subclase para aves que pueden volar class FlyingBird(Bird): def fly(self): print("I can fly!") \# Subclase para aves que no vuelan class NonFlyingBird(Bird): def fly(self): raise NotImplementedError("This bird can't fly.") \# Aves específicas class Eagle(FlyingBird): def make\_sound(self): print("Screech!") class Penguin(NonFlyingBird): def make\_sound(self): print("Honk!") ``` \### Explicación \- \*\*Clase `Bird`\*\*: Es una clase base abstracta que define el método `make\_sound` que todas las aves deben implementar. \- \*\*Clases `FlyingBird` y `NonFlyingBird`\*\*: Son clases intermedias que especifican si un ave puede volar o no. `FlyingBird` implementa el método `fly`, mientras que `NonFlyingBird` levanta una excepción controlada con `NotImplementedError` para indicar que estas aves no vuelan. \- \*\*Clases `Eagle` y `Penguin`\*\*: Estas representan tipos específicos de aves que implementan el método `make\_sound` de la clase base `Bird`. `Eagle` puede volar gracias a que hereda de `FlyingBird`, y `Penguin` no vuela porque hereda de `NonFlyingBird`. \### Uso del Código Podemos crear instancias de `Eagle` y `Penguin` y usarlas de manera segura sin que una viole el comportamiento esperado de la otra. ```python \# Crear instancias eagle = Eagle() penguin = Penguin() \# Ejecutar métodos eagle.make\_sound() # Screech! eagle.fly() # I can fly! penguin.make\_sound() # Honk! \# penguin.fly() # Esto lanza NotImplementedError, lo cual es el comportamiento esperado ``` \### Ventajas de Aplicar el LSP 1\. \*\*Consistencia en el Comportamiento\*\*: Las subclases cumplen con el comportamiento esperado sin generar resultados inesperados o excepciones imprevistas. 2\. \*\*Reusabilidad\*\*: Las clases `FlyingBird` y `NonFlyingBird` actúan como plantillas reutilizables para cualquier tipo de ave que vuele o no vuele, respectivamente. 3\. \*\*Mantenibilidad\*\*: Se puede extender la jerarquía con nuevas clases de aves sin tener que modificar la lógica existente. \### Conclusión El Principio de Sustitución de Liskov nos ayuda a crear una jerarquía de clases que se comporta de manera predecible, cumpliendo con las expectativas del usuario de la clase base. En este ejemplo, hemos creado un diseño que permite agregar nuevos tipos de aves sin romper la funcionalidad de las existentes, cumpliendo así con el LSP y mejorando la estabilidad y extensibilidad del sistema.
En Python, las interfaces se denominan "Protocolos" porque el lenguaje utiliza esta terminología para definir un conjunto de métodos que las clases deben implementar, sin necesidad de heredar de una clase base específica. Esto se importa del módulo `typing` para permitir una programación más flexible y dinámica. Los protocolos proporcionan una forma de definir interfaces de manera más informal y son una característica importante para implementar el principio de sustitución de Liskov. Esto permite que diferentes clases sean intercambiables siempre que cumplan con el mismo protocolo.
Para cumplir con el principio de sustitución de Liskov la clase SMSNotifier no requiere de un parámetro adicional al que pide EMailNotifier ya que heredan de la misma clase. La clase debe quedar: ```js @dataclass class SMSNotifier(Notifier): sms_gateway: str def send_confirmation(self, customer_data: CustomerData): # Responsabilidad de la notificación phone_number = customer_data.contact_info.phone # sms_gateway = "the custom SMS Gateway" print( f"send the sms using {self.sms_gateway}: SMS sent to {phone_number}: \ Thank you for your payment." ) ```@dataclassclass SMSNotifier(Notifier): sms\_gateway: str def send\_confirmation(self, customer\_data: CustomerData): *# Responsabilidad de la notificación* phone\_number = customer\_data.contact\_info.phone *# sms\_gateway = "the custom SMS Gateway"* print( f"send the sms using {*self*.sms\_gateway}: SMS sent to {phone\_number}: \ Thank you for your payment." )
El bug que encontré se debe a que se cambio la clase SMSNotifier, la deje así: ```js @dataclass class SMSNotifier(Notifier): sms_gateway: str def send_confirmation(self, customer_data: CustomerData): # Responsabilidad de la notificación phone_number = customer_data.contact_info.phone print( f"send the sms using {sms_gateway}: SMS sent to {phone_number}: \ Thank you for your payment." ) ```Se quito la linea de código \# sms\_gateway = "the custom SMS Gateway" Hay que cambiar la firma de la función a: def send\_confirmation(self, customer\_data: CustomerData, sms\_gateway): Así que al instanciar el sms\_notifier se debe debe incluir: sms\_notifier = SMSNotifier(sms\_gateway="the custom SMS Gateway") Con estas modificaciones me funciono el código de nuevo.