Problema

Evitar el binding permanente entre una abstracción y su implementación, permitiendo que ambas puedan variar independientemente.

Propósito

Separar una abstracción de su implementación para que ambas puedan evolucionar de forma independiente. Utiliza composición en lugar de herencia para conectar diferentes jerarquías de clases.

Casos de uso comunes

  • Sistemas multiplataforma (Windows, Mac, Linux)
  • Drivers de base de datos (MySQL, PostgreSQL, Oracle)
  • Sistemas de notificación con múltiples canales
  • APIs con diferentes proveedores

¿Quién es quién en Bridge?

Actor Lo que realmente es Ejemplo Analogía
Abstraction Clase de alto nivel que define QUÉ hacer Notification - maneja lógica de notificaciones Control remoto (sabe QUÉ botones presionar)
RefinedAbstraction Variaciones de la abstracción UrgentNotification - agrega urgencia al mensaje Control remoto con botones extra
Implementation Interfaz que define CÓMO hacer MessageSender - define cómo enviar mensajes “Protocolo de comunicación” (interfaz)
ConcreteImplementation Implementaciones reales del CÓMO SMSSender, EmailSender - formas concretas de enviar TV Samsung, TV LG (CÓMO responden)

Diagrama

classDiagram
    namespace Bridge {
        class Abstraction {
            -implementation: Implementation
            +operation()
        }
        
        class RefinedAbstraction {
            +operation()
            +extendedOperation()
        }
        
        class Implementation {
            <<interface>>
            +operationImpl()
        }
        
        class ConcreteImplementationA {
            +operationImpl()
        }
        
        class ConcreteImplementationB {
            +operationImpl()
        }
    }
    
    Abstraction <|-- RefinedAbstraction
    Abstraction --> Implementation
    Implementation <|.. ConcreteImplementationA
    Implementation <|.. ConcreteImplementationB

Ejemplo práctico

classDiagram
    namespace NotificationExample {
        class Notification {
            -sender: MessageSender
            +Notification(sender)
            +send(message, recipient)
        }
        
        class UrgentNotification {
            +UrgentNotification(sender)
            +send(message, recipient)
        }
        
        class SimpleNotification {
            +SimpleNotification(sender)
            +send(message, recipient)
        }
        
        class MessageSender {
            <<interface>>
            +sendMessage(message, recipient)
        }
        
        class EmailSender {
            +sendMessage(message, recipient)
        }
        
        class SMSSender {
            +sendMessage(message, recipient)
        }
        
        class SlackSender {
            +sendMessage(message, recipient)
        }
    }
    
    Notification <|-- UrgentNotification
    Notification <|-- SimpleNotification
    Notification --> MessageSender : "bridge"
    MessageSender <|.. EmailSender
    MessageSender <|.. SMSSender
    MessageSender <|.. SlackSender

Flujo de funcionamiento

sequenceDiagram
    participant Client
    participant UrgentNotification
    participant SMSSender
    
    Client->>UrgentNotification: send("Server down!", "+1234567890")
    Note over UrgentNotification: Modifica el mensaje (agrega 🚨 URGENT)
    UrgentNotification->>SMSSender: sendMessage("🚨 URGENT: Server down!", "+1234567890")
    Note over SMSSender: Implementa el envío real por SMS
    SMSSender-->>UrgentNotification: "sms-sent"
    UrgentNotification-->>Client: "sms-sent"

Flexibilidad del Bridge

El poder del patrón: cualquier abstracción con cualquier implementación

// Puedes combinar CUALQUIER tipo de notificación con CUALQUIER sender
const urgentEmail = new UrgentNotification(new EmailSender());
const urgentSMS = new UrgentNotification(new SMSSender());
const urgentSlack = new UrgentNotification(new SlackSender());

const simpleEmail = new SimpleNotification(new EmailSender());
const simpleSMS = new SimpleNotification(new SMSSender());
const simpleSlack = new SimpleNotification(new SlackSender());

// Todas estas combinaciones funcionan sin modificar código existente
urgentEmail.send("Server down!", "admin@company.com");
simpleSMS.send("Meeting reminder", "+1234567890");

¿Cómo separa abstracción de implementación?

Básicamente es polimorfismo en el constructor, pero el valor está en la SEPARACIÓN:

// ABSTRACCIÓN: Define QUÉ hacer (lógica de negocio)
class UrgentNotification {
    constructor(sender) {
        this.sender = sender; // Bridge: no sabe CÓMO enviar
    }
    
    send(message, recipient) {
        // Lógica de negocio: agregar urgencia
        const urgentMessage = `🚨 URGENT: ${message}`;
        
        // Delega el CÓMO a la implementación
        return this.sender.sendMessage(urgentMessage, recipient);
    }
}

// IMPLEMENTACIÓN: Define CÓMO hacer (detalles técnicos)
class SMSSender {
    sendMessage(message, recipient) {
        // Detalles técnicos: cómo enviar SMS
        console.log(`Connecting to SMS gateway...`);
        console.log(`Sending SMS to ${recipient}: ${message}`);
        return "sms-sent";
    }
}

El valor real: Evolución independiente

Sin Bridge (acoplado):

class UrgentEmailNotification {
    send(message, recipient) {
        const urgentMessage = `🚨 URGENT: ${message}`;
        // Acoplado: sabe cómo enviar email
        console.log(`Email to ${recipient}: ${urgentMessage}`);
    }
}

// Si cambias cómo enviar email, debes tocar TODAS las clases
// Si agregas nuevo tipo, debes crear clases para TODOS los métodos

Con Bridge (desacoplado):

class UrgentNotification {
    constructor(sender) {
        this.sender = sender; // Desacoplado: no sabe cómo enviar
    }
    
    send(message, recipient) {
        const urgentMessage = `🚨 URGENT: ${message}`;
        return this.sender.sendMessage(urgentMessage, recipient);
    }
}

// Cambios en implementación NO afectan abstracción
// Cambios en abstracción NO afectan implementación

Ventajas

  • Desacoplamiento: Abstracción e implementación evolucionan independientemente
  • Extensibilidad: Fácil agregar nuevas abstracciones o implementaciones
  • Flexibilidad: Combinaciones dinámicas en tiempo de ejecución
  • Reutilización: Las implementaciones pueden ser compartidas

Desventajas

  • Complejidad: Introduce más clases e interfaces
  • Indirección: Puede afectar ligeramente el rendimiento
  • Diseño inicial: Requiere planificación previa de las jerarquías

Cuándo usar

  • Quieres evitar binding permanente entre abstracción e implementación
  • Tanto abstracción como implementación deben ser extensibles
  • Necesitas compartir implementaciones entre múltiples abstracciones
  • Cambios en implementación no deben afectar clientes

Cuándo NO usar

  • Solo tienes una abstracción y una implementación
  • La relación entre abstracción e implementación es simple y estable
  • La complejidad adicional no se justifica

Las dos jerarquías independientes

graph TB
    subgraph "Jerarquía 1: Tipos de Notificación (QUÉ hacer)"
        N[Notification]
        N --> UN[UrgentNotification]
        N --> SN[SimpleNotification]
        N --> CN[CriticalNotification]
    end
    
    subgraph "Jerarquía 2: Métodos de Envío (CÓMO hacer)"
        MS[MessageSender Interface]
        MS --> ES[EmailSender]
        MS --> SS[SMSSender]
        MS --> WS[WhatsAppSender]
        MS --> SLS[SlackSender]
    end
    
    N -.->|"Bridge: this.sender"| MS
    
    style N fill:#e1f5fe
    style MS fill:#f3e5f5

El Bridge conecta ambas jerarquías:

  • Jerarquía 1 puede evolucionar independientemente (agregar nuevos tipos)
  • Jerarquía 2 puede evolucionar independientemente (agregar nuevos métodos)
  • Cualquier combinación funciona automáticamente

Matriz de combinaciones:

Abstracción \ Implementación EmailSender SMSSender WhatsAppSender SlackSender
UrgentNotification
SimpleNotification
CriticalNotification

12 combinaciones con solo 7 clases

Diferencias con otros patrones

  • vs Adapter: Bridge planifica flexibilidad, Adapter arregla incompatibilidad
  • vs Strategy: Bridge separa jerarquías, Strategy intercambia algoritmos