Cómo usar clases abstractas en Python
Clase 6 de 27 • Curso de Patrones de Diseño y SOLID en Python
Contenido del curso
Principios SOLID
- 2

Principio de responsabilidad única en SOLID
05:59 min - 3

Refactorizando código Python con principios SOLID
11:14 min - 4

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

Open Closed Principle: extensión sin modificación
02:39 min - 6

Cómo usar clases abstractas en Python
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

Interface Segregation: cuándo separar contratos
02:33 min - 10

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

Principio de inversión de dependencias explicado
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:05 min - 18

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

Patrón Decorator en 5 pasos para funcionalidad dinámica
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 para servicios de pagos
18:55 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

Arquitectura robusta para procesadores de pago
03:19 min
Aplicar el principio abierto cerrado con Python es más simple de lo que parece: abre tu código a la extensión y ciérralo a la modificación usando modelos tipados con Pydantic y clases abstractas para comportamientos como el procesador de pagos y el notificador. Así se añade una nueva pasarela o un canal de notificación sin tocar la base.
¿Cómo aplicar el principio abierto cerrado en Python con Pydantic y clases abstractas?
El objetivo es incorporar una nueva pasarela de pagos sin reescribir lógica existente. Para ello, se reorganiza el proyecto y se tipan los datos con Pydantic. Luego, se define la forma del procesador de pagos mediante una clase abstracta, y las implementaciones concretas heredan esa forma.
- Nuevo requerimiento: agregar una pasarela de pagos adicional.
- Carpeta dedicada al principio open close con “antes” y “después”.
- Migración de estructuras a modelos Pydantic para validación y tipado.
Ejemplo de modelos con Pydantic y acceso por atributos en vez de diccionario:
from pydantic import BaseModel
from typing import Optional
class ContactInfo(BaseModel):
email: Optional[str]
phone: Optional[str]
class CustomerData(BaseModel):
name: int # según el ejemplo, tipado como entero.
contactInfo: ContactInfo
class PaymentData(BaseModel):
amount: int
source: str
¿Qué valida Pydantic y cómo cambia el acceso a datos?
- Define forma y tipos: por ejemplo,
amountentero ysourcestring. - Maneja opcionales:
emailyphonepueden serNoneostr. - Facilita validaciones: ahora se usa
customerData.nameen lugar de claves de diccionario.
¿Qué cambios habilitan la extensión sin modificar el servicio?
El paso clave es depender de una abstracción. Se crea la clase abstracta del procesador de pagos con from abc import ABC, abstractmethod. La implementación concreta (por ejemplo, Stripe) hereda y define la lógica. Luego, el servicio depende de la abstracción, no de la clase concreta. Con data classes y field(default_factory=...) se fijan implementaciones por defecto sin acoplar el código.
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
class PaymentProcessor(ABC):
@abstractmethod
def processTransaction(self, paymentData: PaymentData) -> None:
...
class StripePaymentProcessor(PaymentProcessor):
def processTransaction(self, paymentData: PaymentData) -> None:
# lógica real de Stripe aquí.
...
@dataclass
class PaymentService:
paymentProcessor: PaymentProcessor = field(default_factory=StripePaymentProcessor)
# notifier se define más abajo.
def pay(self, paymentData: PaymentData, customerData: CustomerData) -> None:
self.paymentProcessor.processTransaction(paymentData)
self.notifier.sendConfirmation(customerData)
- Dependencia de abstracciones: el servicio ya no acopla a
StripePaymentProcessor. - Uso de polimorfismo: nuevas pasarelas implementan
PaymentProcessorsin tocar el servicio. - field(default_factory=StripePaymentProcessor) crea la instancia por defecto y evita dependencias rígidas.
¿Cómo configurar y probar notificaciones por email y SMS?
Se repite el patrón con un notificador: una clase abstracta define la firma de sendConfirmation y dos implementaciones (EmailNotifier y SMSNotifier) envían por distintos canales. Se refactoriza para no validar lo que la implementación ya garantiza (por ejemplo, en EmailNotifier se asume email disponible y se manejan casos vacíos con una condición simple).
¿Cómo se abstrae el notificador?
class Notifier(ABC):
@abstractmethod
def sendConfirmation(self, customerData: CustomerData) -> None:
...
class EmailNotifier(Notifier):
def sendConfirmation(self, customerData: CustomerData) -> None:
email = customerData.contactInfo.email or ""
if not email:
# evitar error cuando email es None o vacío.
return
# enviar correo a email.
...
class SMSNotifier(Notifier):
def sendConfirmation(self, customerData: CustomerData) -> None:
phone = customerData.contactInfo.phone
# enviar SMS simulado a phone.
...
Integración en el servicio con notificador por defecto y configuración flexible:
@dataclass
class PaymentService:
paymentProcessor: PaymentProcessor = field(default_factory=StripePaymentProcessor)
notifier: Notifier = field(default_factory=EmailNotifier)
def pay(self, paymentData: PaymentData, customerData: CustomerData) -> None:
self.paymentProcessor.processTransaction(paymentData)
self.notifier.sendConfirmation(customerData)
# Cambiar comportamiento sin modificar la base de código:
service = PaymentService(notifier=SMSNotifier())
¿Qué comportamiento se observa al ejecutar?
- Al forzar
SMSNotifier, no se envía SMS si falta teléfono. - Si hay teléfono, el SMS sale “enviado” al número definido.
- Sin pasar notificador, usa el predeterminado: correo electrónico.
En conjunto, se aplicó el principio abierto cerrado con: modelos Pydantic tipados, clases abstractas (PaymentProcessor, Notifier), implementaciones concretas (StripePaymentProcessor, EmailNotifier, SMSNotifier), y default factory en data classes para configurar por defecto. Comparte en comentarios: ¿de qué otra forma aplicarías este principio aquí y qué mejoras propondrías al diseño?