Patrones de diseño de Python

Los patrones de diseño son, en el desarrollo de software, para problemas comunessoluciones reutilizables. No son diseños completos que se puedan convertir directamente en código, sino para resolver problemas específicosplantillas o planos。

El valor central de los patrones de diseño

Los patrones de diseño son como planos estándar en el campo de la arquitectura, y proporcionan los siguientes beneficios para el desarrollo de software:

  • Mejora la reutilización del código: evita reinventar la rueda
  • Mejora la mantenibilidad del código: hace que la estructura del código sea más clara
  • Promueve la colaboración en equipo: proporciona un lenguaje de programación y una forma de pensar unificados
  • Mejora la calidad del código: las soluciones probadas en la práctica son más confiables

Analogía de los patrones de diseño en la vida cotidiana

Imagina que vas a construir una casa:

  • Método original: diseñar desde cero cada vez, propenso a errores e ineficiente
  • Uso de patrones de diseño: usar planos de construcción estándar, conocer la distribución estándar de sala, cocina y dormitorio

Los patrones de diseño son los "planos de construcción estándar" del desarrollo de software.

Tipos de patrones de diseño

Según la obra clásica«Design Patterns - Elements of Reusable Object-Oriented Software» (traducción al chino: «Patrones de diseño: elementos de software orientado a objetos reutilizables»)de la definición, hay 23 patrones de diseño clásicos en total. Estos patrones se pueden dividir en tres categorías según su enfoque: patrones creacionales (Creational Patterns), patrones estructurales (Structural Patterns) y patrones de comportamiento (Behavioral Patterns). Además, en el desarrollo a nivel empresarial, también hay una categoría de patrones de diseño a nivel de arquitectura de uso común.

N.ºPatrón y descripciónIncluye
1 Patrones creacionales
Se utilizan para resolver problemas de creación de objetos. Al encapsular la lógica de instanciación y ocultar los detalles de implementación, hacen que el sistema sea más flexible y extensible al crear objetos.
  • Patrón de fábrica (Factory Pattern)
  • Patrón de fábrica abstracta (Abstract Factory Pattern)
  • Patrón Singleton (Singleton Pattern)
  • Patrón Builder (Builder Pattern)
  • Patrón Prototype (Prototype Pattern)
2 Patrones estructurales
Se centran en la composición y colaboración entre clases y objetos, ayudándonos a construir estructuras de sistema flexibles, reutilizables y extensibles.
  • Patrón Adapter (Adapter Pattern)
  • Patrón Bridge (Bridge Pattern)
  • Patrón de filtro (Filter / Criteria Pattern)
  • Patrón Composite (Composite Pattern)
  • Patrón Decorator (Decorator Pattern)
  • Patrón Facade (Facade Pattern)
  • Patrón Flyweight (Flyweight Pattern)
  • Patrón Proxy (Proxy Pattern)
3 Patrones de comportamiento
Se centran en la comunicación y asignación de responsabilidades entre objetos. Al encapsular algoritmos, estados o lógica de solicitudes, mejoran la flexibilidad y mantenibilidad del sistema.
  • Patrón de cadena de responsabilidad (Chain of Responsibility Pattern)
  • Patrón Command (Command Pattern)
  • Patrón Interpreter (Interpreter Pattern)
  • Patrón Iterator (Iterator Pattern)
  • Patrón Mediator (Mediator Pattern)
  • Patrón Memento (Memento Pattern)
  • Patrón Observer (Observer Pattern)
  • Patrón State (State Pattern)
  • Patrón de objeto nulo (Null Object Pattern)
  • Patrón Strategy (Strategy Pattern)
  • Patrón de método plantilla (Template Method Pattern)
  • Patrón Visitor (Visitor Pattern)
4 Patrones de arquitectura empresarial
Estos patrones se utilizan comúnmente en arquitecturas en capas de sistemas grandes, centrándose en la colaboración entre la capa de presentación y la capa de lógica de negocio.
  • Patrón MVC (Model-View-Controller Pattern)
  • Patrón Business Delegate (Business Delegate Pattern)
  • Patrón de entidad compuesta (Composite Entity Pattern)
  • Patrón de objeto de acceso a datos (Data Access Object Pattern)
  • Patrón Front Controller (Front Controller Pattern)
  • Patrón de filtro de intercepción (Intercepting Filter Pattern)
  • Patrón Service Locator (Service Locator Pattern)
  • Patrón de objeto de transferencia (Transfer Object Pattern)

La siguiente imagen muestra las relaciones y la estructura jerárquica entre los diversos tipos de patrones de diseño:

设计模式之间的关系

Ventajas de los patrones de diseño

  • Proporcionan un lenguaje de diseño unificado, permitiendo a los desarrolladores comunicar rápidamente la intención del diseño.
  • Ofrecen soluciones maduras y reutilizables para problemas comunes.
  • Reducen el acoplamiento del sistema y mejoran la mantenibilidad y extensibilidad del código.
  • Reducen el diseño repetido, mejorando la eficiencia del desarrollo y la calidad del código.
  • Ayudan a los nuevos miembros a comprender rápidamente la arquitectura del sistema y las ideas de diseño.

Los seis principios de los patrones de diseño

1. Principio de apertura y cierre (Open-Closed Principle)

Abierto a la extensión, cerrado a la modificación. El software debe adaptarse a los cambios mediante la extensión de nuevas funcionalidades, en lugar de modificar el código existente. El núcleo de la implementación de este principio radica en usar abstracciones (interfaces o clases base) para definir comportamientos estables.

2. Principio de sustitución de Liskov (Liskov Substitution Principle)

Dondequiera que aparezca una clase base, las subclases deberían poder reemplazarla. La herencia solo tiene sentido cuando las clases derivadas pueden sustituir completamente a la clase base. Este principio garantiza la corrección del polimorfismo y es un complemento importante del principio de apertura y cierre.

3. Principio de inversión de dependencias (Dependency Inversion Principle)

Los módulos de alto nivel no deben depender de los módulos de bajo nivel; ambos deben depender de abstracciones. Las implementaciones concretas deben depender de interfaces o clases abstractas, en lugar de depender directamente de clases concretas. Este principio hace que el sistema sea más fácil de extender y probar.

4. Principio de segregación de interfaces (Interface Segregation Principle)

Una clase no debería depender de interfaces que no necesita. Las interfaces grandes deben dividirse en interfaces más pequeñas y más específicas para reducir el acoplamiento y aumentar la flexibilidad.

5. Ley de Deméter (Law of Demeter)

También conocida como "principio del menor conocimiento": un objeto debe minimizar su interacción con otros objetos. En otras palabras, una clase solo debería conocer los objetos directamente relacionados con ella, reduciendo así la complejidad del sistema.

6. Principio de reutilización compuesta (Composite Reuse Principle)

Se prefiere la composición o agregación para lograr la reutilización en lugar de la herencia. La herencia causa un fuerte acoplamiento, mientras que la composición permite reemplazar dependencias de manera flexible en tiempo de ejecución, logrando un diseño estructural más elegante.


Ejemplos de patrones de diseño comunes en Python

Entendamos la aplicación de los patrones de diseño en Python a través de algunos ejemplos concretos.

Patrón Singleton

El patrón Singleton asegura que una clase tenga una única instancia y proporciona un punto de acceso global.

Ejemplo

class DatabaseConnection:
    _instance = None
   
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            print("Crear una nueva conexión de base de datos")
        return cls._instance
   
    def connect(self):
        print("Conectar a la base de datos")

# Ejemplo de uso
db1 = DatabaseConnection()
db2 = DatabaseConnection()

print(f"¿Son db1 y db2 la misma instancia? {db1 is db2}")  # Salida: True

Escenarios de aplicación:

  • Pool de conexiones de base de datos
  • Administrador de configuración
  • Registrador de logs

Patrón Factory

El patrón de fábrica proporciona una interfaz para crear objetos, permitiendo que las subclases decidan qué clase instanciar.

Ejemplo

from abc import ABC, abstractmethod

# Producto abstracto
class Notification(ABC):
    @abstractmethod
    def send(self, message: str):
        pass

# Producto concreto
class EmailNotification(Notification):
    def send(self, message: str):
        print(f"Enviar correo: {message}")

class SMSNotification(Notification):
    def send(self, message: str):
        print(f"Enviar SMS: {message}")

# Clase de fábrica
class NotificationFactory:
    @staticmethod
    def create_notification(notification_type: str) -> Notification:
        if notification_type == "email":
            return EmailNotification()
        elif notification_type == "sms":
            return SMSNotification()
        else:
            raise ValueError("Tipo de notificación no compatible")

# Ejemplo de uso
email = NotificationFactory.create_notification("email")
sms = NotificationFactory.create_notification("sms")

email.send("Su pedido ha sido enviado")
sms.send("Código de verificación: 123456")

Explicación del código:

  • NotificationEs una clase base abstracta que define la interfaz
  • EmailNotificationySMSNotificationEs una implementación concreta
  • NotificationFactorySe encarga de crear objetos de notificación concretos

Patrón Observer

El patrón de observador define una relación de dependencia de uno a muchos entre objetos; cuando un objeto cambia de estado, todos los objetos que dependen de él reciben una notificación.

Ejemplo

from abc import ABC, abstractmethod

# Interfaz de observador
class Observer(ABC):
    @abstractmethod
    def update(self, message: str):
        pass

# Observador concreto
class EmailSubscriber(Observer):
    def __init__(self, name: str):
        self.name = name
   
    def update(self, message: str):
        print(f"{self.name} recibió notificación por correo: {message}")

class SMSSubscriber(Observer):
    def __init__(self, name: str):
        self.name = name
   
    def update(self, message: str):
        print(f"{self.name} recibió notificación por SMS: {message}")

# Sujeto (observado)
class NewsPublisher:
    def __init__(self):
        self._subscribers = []
   
    def subscribe(self, subscriber: Observer):
        self._subscribers.append(subscriber)
   
    def unsubscribe(self, subscriber: Observer):
        self._subscribers.remove(subscriber)
   
    def notify_subscribers(self, message: str):
        for subscriber in self._subscribers:
            subscriber.update(message)

# Ejemplo de uso
publisher = NewsPublisher()

# Crear suscriptores
alice = EmailSubscriber("Alice")
bob = SMSSubscriber("Bob")

# Suscribirse a noticias
publisher.subscribe(alice)
publisher.subscribe(bob)

# Publicar noticias
publisher.notify_subscribers("¡Python 3.12 ha sido lanzado!")

# Cancelar suscripción
publisher.unsubscribe(alice)
publisher.notify_subscribers("Esta es una notificación que solo Bob puede ver")

Consideraciones especiales de los patrones de diseño en Python

Las características dinámicas de Python

Python, al ser un lenguaje dinámico, hace que algunos patrones de diseño sean más simples de implementar:

Ejemplo

# Fábrica simple al estilo Python
def create_payment(method):
    payment_methods = {
        'credit_card': CreditCardPayment,
        'paypal': PayPalPayment,
        'alipay': AlipayPayment
    }
    return payment_methods[method]()

# Uso directo, sin necesidad de una jerarquía de clases compleja
payment = create_payment('alipay')

Soporte integrado para el patrón Decorator

Python tiene sintaxis de decoradores integrada, lo que hace que la implementación del patrón decorador sea más elegante:

Ejemplo

def log_execution_time(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} tiempo de ejecución: {end - start:.2f} segundos")
        return result
    return wrapper

@log_execution_time
def process_data(data):
    # Simular procesamiento de datos
    import time
    time.sleep(1)
    return f"Datos procesados: {data}"

# Uso
result = process_data("Datos de ejemplo")
Otras extensiones