Patrón Puente en Python

El patrón Puente es un patrón de diseño estructural que separa la parte abstracta de la parte de implementación, permitiendo que ambas cambien de forma independiente. En pocas palabras, esseparar las múltiples dimensiones de variación de una cosa para que cada dimensión pueda extenderse de forma independiente.。

Analogía del patrón Puente en la vida cotidiana

Imagina la relación entre un control remoto y un televisor:

  • Control remoto(parte abstracta): proporciona la interfaz de control (encendido, volumen, canal)
  • Televisor(parte de implementación): ejecuta las operaciones concretas (TV Sony, TV Samsung, TV Xiaomi)

El control remoto no necesita saber cuál es el televisor concreto, ni el televisor necesita saber cuál es el control remoto; se conectan a través de un "puente" y cada uno puede actualizarse y renovarse de forma independiente.


¿Por qué se necesita el patrón Puente?

Limitaciones del enfoque tradicional

Supongamos que queremos diseñar un sistema de dibujo de formas que admita diferentes formas y colores:

Ejemplo

# Implementación tradicional: problema de explosión de clases
class RedCircle:
    def draw(self):
        print("Dibujar círculo de color rojo")

class BlueCircle:
    def draw(self):
        print("Dibujar círculo de color azul")

class RedRectangle:
    def draw(self):
        print("Dibujar rectángulo de color rojo")

class BlueRectangle:
    def draw(self):
        print("Dibujar rectángulo de color azul")

# Cada vez que se añade un color o una forma, es necesario crear una nueva clase
# ¡3 colores × 3 formas = 9 clases!

Ventajas del patrón Puente

Ejemplo

# Implementación con patrón Puente
class Color:
    """Interfaz de color - parte de implementación"""
    def apply_color(self):
        pass

class Shape:
    """Abstracción de forma - parte abstracta"""
    def __init__(self, color):
        self.color = color  # ¡La clave del puente!
   
    def draw(self):
        pass

# Solo se necesitan: 3 colores + 3 formas = 6 clases
# En lugar de: 3 × 3 = 9 clases

Estructura central del patrón Puente

Entendamos los componentes del patrón Puente con un ejemplo completo:

Cuatro roles clave

  1. Parte abstracta (Abstraction): define la interfaz abstracta y mantiene una referencia a la parte de implementación
  2. Abstracción refinada (RefinedAbstraction): extiende la interfaz abstracta
  3. Parte de implementación (Implementor): define la interfaz de implementación
  4. Implementación concreta (ConcreteImplementor): implementa concretamente la interfaz

Ejemplo de código completo: sistema de dibujo de formas

Paso 1: Definir la interfaz de la parte de implementación (color)

Ejemplo

from abc import ABC, abstractmethod

class Color(ABC):
    """Interfaz de color - parte de implementación"""
    @abstractmethod
    def apply_color(self):
        pass

class Red(Color):
    """Implementación concreta - rojo"""
    def apply_color(self):
        return "rojo"

class Blue(Color):
    """Implementación concreta - azul"""
    def apply_color(self):
        return "azul"

class Green(Color):
    """Implementación concreta - verde"""
    def apply_color(self):
        return "verde"

Paso 2: Definir la parte abstracta (forma)

Ejemplo

class Shape(ABC):
    """Abstracción de forma - parte abstracta"""
    def __init__(self, color: Color):
        self.color = color  # Clave del puente: mantener una referencia a la parte de implementación
   
    @abstractmethod
    def draw(self):
        pass

class Circle(Shape):
    """Abstracción concreta - círculo"""
    def draw(self):
        color_name = self.color.apply_color()
        print(f"Dibujar círculo de color {color_name}")

class Rectangle(Shape):
    """Abstracción concreta - rectángulo"""
    def draw(self):
        color_name = self.color.apply_color()
        print(f"Dibujar rectángulo de color {color_name}")

class Triangle(Shape):
    """Abstracción concreta - triángulo"""
    def draw(self):
        color_name = self.color.apply_color()
        print(f"Dibujar triángulo de color {color_name}")

Paso 3: Usar el patrón Puente

Ejemplo

# Código del cliente
def main():
    # Crear implementaciones de color
    red = Red()
    blue = Blue()
    green = Green()
   
    # Crear la abstracción de forma y vincular la implementación de color
    red_circle = Circle(red)
    blue_rectangle = Rectangle(blue)
    green_triangle = Triangle(green)
   
    # Dibujar formas
    print("=== Resultado del dibujo de formas ===")
    red_circle.draw()        # Salida: Dibujar círculo de color rojo
    blue_rectangle.draw()    # Salida: Dibujar rectángulo de color azul
    green_triangle.draw()    # Salida: Dibujar triángulo de color verde
   
    # Combinación flexible: rectángulo rojo
    red_rectangle = Rectangle(red)
    red_rectangle.draw()     # Salida: Dibujar rectángulo de color rojo

if __name__ == "__main__":
    main()

Escenarios de aplicación reales

Escenario 1: Sistema de envío de mensajes

Ejemplo

from abc import ABC, abstractmethod

# Parte de implementación: método de envío de mensajes
class MessageSender(ABC):
    @abstractmethod
    def send(self, message):
        pass

class EmailSender(MessageSender):
    def send(self, message):
        return f"Enviar por correo electrónico: {message}"

class SMSSender(MessageSender):
    def send(self, message):
        return f"Enviar por SMS: {message}"

class WeChatSender(MessageSender):
    def send(self, message):
        return f"Enviar por WeChat: {message}"

# Parte abstracta: tipo de mensaje
class Message(ABC):
    def __init__(self, sender: MessageSender):
        self.sender = sender
   
    @abstractmethod
    def send(self):
        pass

class UrgentMessage(Message):
    def send(self):
        message = "[Urgente] " + self.get_content()
        return self.sender.send(message)
   
    def get_content(self):
        return "¡El sistema presenta una anomalía, atiéndalo de inmediato!"

class NormalMessage(Message):
    def __init__(self, sender: MessageSender, content):
        super().__init__(sender)
        self.content = content
   
    def send(self):
        return self.sender.send(self.content)

# Ejemplo de uso
def message_example():
    email_sender = EmailSender()
    sms_sender = SMSSender()
   
    # Mensaje urgente enviado por SMS
    urgent_sms = UrgentMessage(sms_sender)
    print(urgent_sms.send())  # Enviado por SMS: [Urgente] ¡El sistema presenta una anomalía, atiéndalo de inmediato!
   
    # Mensaje normal enviado por correo electrónico
    normal_email = NormalMessage(email_sender, "El informe mensual ha sido generado")
    print(normal_email.send())  # Enviado por correo electrónico: El informe mensual ha sido generado

message_example()

Escenario 2: Sistema de control remoto de dispositivos

Ejemplo

# Parte de implementación: dispositivo
class Device(ABC):
    @abstractmethod
    def turn_on(self):
        pass
   
    @abstractmethod
    def turn_off(self):
        pass
   
    @abstractmethod
    def set_volume(self, volume):
        pass

class TV(Device):
    def turn_on(self):
        return "Televisor encendido"
   
    def turn_off(self):
        return "Televisor apagado"
   
    def set_volume(self, volume):
        return f"Volumen del televisor establecido en: {volume}"

class Radio(Device):
    def turn_on(self):
        return "Radio encendida"
   
    def turn_off(self):
        return "Radio apagada"
   
    def set_volume(self, volume):
        return f"Volumen de la radio establecido en: {volume}"

# Parte abstracta: control remoto
class RemoteControl:
    def __init__(self, device: Device):
        self.device = device
   
    def toggle_power(self):
        return "Cambiar estado de encendido"
   
    def volume_up(self):
        return "Aumentar volumen"
   
    def volume_down(self):
        return "Reducir volumen"

class AdvancedRemoteControl(RemoteControl):
    def mute(self):
        return "Modo silencio"
   
    def set_channel(self, channel):
        return f"Cambiar al canal: {channel}"

# Ejemplo de uso
def remote_example():
    tv = TV()
    radio = Radio()
   
    basic_remote = RemoteControl(tv)
    advanced_remote = AdvancedRemoteControl(radio)
   
    print(basic_remote.toggle_power())  # Cambiar estado de encendido
    print(advanced_remote.mute())       # Modo silencio

remote_example()

Comparación de ventajas del patrón Puente

Característica Enfoque tradicional Patrón Puente
Escalabilidad Mala, requiere modificar el código existente Buena, se puede extender de forma independiente
Número de clases Muchas, propenso a la explosión de clases Pocas, crecimiento lineal
Mantenibilidad Difícil, un cambio afecta a todo Fácil, separación de responsabilidades
Flexibilidad Combinación fija Combinación dinámica en tiempo de ejecución

Mejores prácticas y consideraciones

Cuándo usar

✅ Casos adecuados para usar el patrón Puente:

  • Cuando se necesita una extensión independiente en múltiples dimensiones
  • Cuando se necesita cambiar la implementación en tiempo de ejecución
  • Cuando se necesita evitar la vinculación permanente entre la abstracción y la implementación
  • Cuando la herencia provocaría una jerarquía de clases demasiado compleja

❌ Casos no adecuados:

  • Cuando hay pocas dimensiones de variación y no se necesita un diseño complejo
  • Cuando existe un fuerte acoplamiento entre la abstracción y la implementación

Errores comunes y cómo evitarlos

Ejemplo

# Ejemplo incorrecto: no hay un desacoplamiento real
class BadShape:
    def __init__(self, color_type):
        if color_type == "red":
            self.color = Red()
        elif color_type == "blue":
            self.color = Blue()
        # Aún se debe modificar esta clase para añadir un nuevo color

# Forma correcta: inyección de dependencias
class GoodShape:
    def __init__(self, color: Color):  # Depender de la interfaz, no de la implementación concreta
        self.color = color

Ejercicios

Ejercicio 1: Extender el sistema de formas

Añade las siguientes funcionalidades al sistema de formas:

  1. Añadir un nuevo color (amarillo)
  2. Añadir una nueva forma (elipse)
  3. Crear una elipse amarilla y dibujarla

Ejercicio 2: Diseñar un sistema de pagos

Diseña un sistema de pagos con los siguientes requisitos:

  • Métodos de pago: Alipay, WeChat Pay, tarjeta bancaria
  • Tipos de pago: pago normal, pago a plazos, pago combinado
  • Implementado con el patrón puente para garantizar que los métodos de pago y los tipos de pago puedan extenderse de forma independiente

Resumen

El patrón puente, a través dela separación de la abstracción y la implementación, ofrece una gran flexibilidad. Su idea central es:

  1. Identificar las dimensiones de cambio: encontrar las múltiples dimensiones del sistema que pueden cambiar de forma independiente
  2. Establecer un puente: conectar diferentes dimensiones mediante relaciones de composición
  3. Extensión independiente: cada dimensión puede desarrollarse y cambiar de forma independiente
Otras extensiones