Contenido del curso
Principios SOLID
- 2

Qué es el principio de responsabilidad única
05:58 min - 3

Procesador de pagos en Python con Stripe
11:13 min - 4

Cómo aplicar SRP en un procesador de pagos con Stripe
25:19 min - 5

Principio open-closed en Python
02:39 min - 6

Clases abstractas y principio abierto-cerrado
Viendo ahora - 7

Principio de Liskov en S.O.L.I.D.
03:18 min - 8

Principio de sustitución de Liskov en Python
06:38 min - 9

Principio de segregación de interfaces en SOLID
02:33 min - 10

Segregación de interfaces en procesadores de pagos
09:05 min - 11

Principio de inversión de dependencias en SOLID
04:13 min - 12

Principio de inversión de dependencias: servicio de pagos flexible
05:56 min
Reestructuración del proyecto
Patrones de Diseño
- 14

Qué son los patrones de diseño: definición y categorías
03:54 min - 15

Strategy Pattern con Python y setprocessor
01:55 min - 16

Strategy Pattern para pagos en Python
10:58 min - 17

Factory Pattern: centralizar creación de objetos
03:04 min - 18

Patrón Factory para procesar pagos con match
11:06 min - 19

Qué es el patrón Decorator
03:06 min - 20

Patrón decorador en servicios de pagos
12:57 min - 21

Builder Pattern: construcción paso a paso
01:28 min - 22

Builder Pattern aplicado a un servicio de pagos
18:54 min - 23

Observer Pattern en sistemas de eventos
01:48 min - 24

Observer en sistemas de pagos con Python
11:11 min - 25

Chain of Responsibility para validar pagos
02:04 min - 26

Chain of Responsibility en servicios de pagos
16:27 min - 27

Código funcional vs código mantenible en Python
03:19 min
Clases abstractas y principio abierto-cerrado
Resumen
El principio abierto-cerrado establece que tu código debe estar abierto a la extensión y cerrado a la modificación. En la práctica, eso significa que puedes añadir nuevas funcionalidades, como una pasarela de pagos distinta, sin tocar la lógica que ya funciona. Si trabajas con Python y quieres escribir código mantenible, este principio es uno de los pilares de SOLID que más te va a ahorrar dolores de cabeza.
¿Qué es el principio abierto-cerrado y por qué importa?
La idea es simple: tu base de código va a cambiar siempre, pero los cambios no deberían romper lo que ya está probado. En lugar de modificar una clase existente cada vez que aparece un requerimiento nuevo, la extiendes mediante polimorfismo, clases abstractas o interfaces.
¿Qué significa abierto a extensión y cerrado a modificación? Significa que puedes agregar comportamiento nuevo creando clases que hereden de una abstracción, sin alterar el código que ya usa esa abstracción.
En el caso del procesador de pagos, aparece un requerimiento típico [01:10]: integrar una nueva pasarela además de Stripe. Si tu servicio depende directamente de StripePaymentProcessor, cada nueva pasarela te obliga a reescribir el servicio. Con una abstracción intermedia, no.
¿Cómo preparar los datos con Pydantic antes de aplicar el principio?
Antes de tocar la arquitectura, conviene tipar bien los datos. Aquí entra Pydantic, la librería más usada en el ecosistema de Python para representar y validar datos [01:45].
En el refactor se reemplazaron los diccionarios por modelos de Pydantic:
PaymentDatacon unamountentero y unsourcestring.CustomerDataconnamey unContactInfoanidado.ContactInfoconemailyphonecomoOptional[str].
El beneficio es doble. Tienes validación automática y, dentro de los métodos, accedes a los datos como atributos customer_data.name en lugar de claves de diccionario. Eso le da forma clara a las firmas de métodos como CustomerValidator, que ahora declara customer_data: CustomerData.
¿Cómo aplicar el principio abierto-cerrado con clases abstractas en Python?
La estrategia es crear una abstracción de la que dependa el servicio, y luego implementaciones concretas que hereden de ella. En el código se aplica a dos piezas: el procesador de pagos y el notificador.
¿Cómo crear una clase abstracta para el procesador de pagos?
Python trae el módulo abc para esto. El proceso es directo [04:20]:
- Importar
ABCyabstractmethoddesdeabc. - Crear
class PaymentProcessor(ABC)con la firma deprocess_transactiondecorada con@abstractmethod, sin lógica. - Hacer que
StripePaymentProcessorherede dePaymentProcessore implementeprocess_transaction.
La clase abstracta define la forma, la clase concreta define la lógica. Esa separación es la que te permite extender sin modificar.
¿Cuál es la diferencia entre una clase abstracta y su implementación? La abstracta declara qué métodos deben existir y con qué firma; la implementación contiene el código real que se ejecuta. El servicio depende de la abstracta, no de la concreta.
¿Cómo inyectar la dependencia en el servicio sin acoplarlo?
El PaymentService deja de declarar payment_processor: StripePaymentProcessor y pasa a declarar payment_processor: PaymentProcessor. Para que la data class siga teniendo un valor por defecto, se usa field(default_factory=StripePaymentProcessor), importando field desde dataclasses [07:30].
Con eso logras dos cosas a la vez:
- El servicio depende de una abstracción, no de Stripe.
- Sigue funcionando sin configuración explícita gracias al default factory.
Si mañana llega PayPal, creas PayPalPaymentProcessor(PaymentProcessor) y lo inyectas. El servicio ni se entera.
¿Cómo extender el notificador a email y SMS sin romper el servicio?
El notificador originalmente mezclaba el envío por email y por teléfono en la misma clase. La solución sigue el mismo patrón [09:15]:
- Crear una clase abstracta
Notifier(ABC)con un métodosend_confirmation(customer_data: CustomerData)decorado con@abstractmethod. - Renombrar la clase existente a
EmailNotifier(Notifier)y dejar solo la lógica de envío por correo. - Crear
SMSNotifier(Notifier)que extrae el número de teléfono delCustomerDatay envía el mensaje.
Un detalle importante: dentro de EmailNotifier ya no tiene sentido validar si el email existe en ContactInfo, porque la clase asume que ese canal está disponible. Aun así, como email es Optional[str], conviene una condición mínima para evitar errores de tipado.
Después, el PaymentService declara notifier: Notifier = field(default_factory=EmailNotifier). Por defecto notifica por correo, pero acepta cualquier implementación de Notifier.
¿Cómo cambiar el comportamiento del servicio sin tocar su código?
En el bloque de uso al final del archivo [13:40], el servicio se construye así:
python sms_notifier = SMSNotifier() service = PaymentService(notifier=sms_notifier)
Con eso le dices al servicio que todas las notificaciones se envíen por mensaje de texto. Si no pasas el parámetro, usa el EmailNotifier por defecto. Al ejecutar el código se ven tres escenarios:
- Cliente con email pero sin teléfono: el SMS sale con
Nonecomo destino. - Cliente con teléfono: el SMS llega correctamente al número.
- Tarjeta declinada: el procesador devuelve el error sin notificar.
Luego, eliminando el notifier=sms_notifier, el servicio vuelve a notificar por correo sin que hayas modificado una sola línea de PaymentService. Ahí está el principio aplicado: abierto a extensión, cerrado a modificación.
¿De qué otra forma aplicarías el principio abierto-cerrado a este código? ¿Qué mejoras le harías al diseño del servicio? Déjalo en los comentarios.