🚨 ¡Nueva review! 🔇 Los mejores cascos con ANC del mercado: los Sony WH-1000XM4 . ¡Échale un ojo! 👀

El Patrón de diseño Bridge

Desacopla la Abstracción de la Implementación. ¡Construye puentes!

Escrito por domin el 5 de noviembre de 2025 · Actualizado el 8 de febrero de 2026

🌉 El Patrón Bridge: Separa el qué del cómo

Tienes un sistema de notificaciones y necesitas enviar mensajes de distintos tipos (simples, advertencias, urgentes) por distintos canales (Email, SMS, Push). Y de repente te das cuenta de que estás creando una clase para cada combinación: SmsAdvertencia, EmailAdvertencia, SmsInformativa… y la cosa no para de crecer.

Pues el Patrón Bridge existe exactamente para esto. Monta un puente que conecta la parte de qué es (la Abstracción, por ejemplo un Mensaje de Advertencia) con la parte de cómo se hace (la Implementación, por ejemplo Enviar por Email). Y lo mejor es que cada lado va por libre, sin enterarse de lo que hace el otro.

En vez de crear una clase por cada combinación posible, separas las dos dimensiones y las conectas por una interfaz. Así cada lado crece sin molestar al otro.

Diagrama del Patrón Bridge mostrando la Abstracción enlazada a través de una interfaz con la Implementación.

¿Cuál es el objetivo principal del Patrón Bridge?

El problema del “Producto Cartesiano de Clases”

Antes de meternos en la solución, vamos a ver bien el percal. Imagina que tienes 3 tipos de mensaje (Simple, Advertencia, Urgente) y 3 canales (Email, SMS, Push). Sin Bridge acabas con 9 clases:

EmailSMSPush
SimpleSimpleEmailSimpleSmsSimplePush
AdvertenciaWarningEmailWarningSmsWarningPush
UrgenteUrgentEmailUrgentSmsUrgentPush

Si añades un cuarto canal (WhatsApp), suben a 12 clases. Si añades un cuarto tipo de mensaje, suben a 16. Crece como N × M y se vuelve inmanejable. Se te va de las manos rapidísimo.

Con Bridge tienes N + M clases: 3 tipos de mensaje + 3 canales = 6 clases. Y si añades WhatsApp, son 7. No 12. Esa es la cosa.

Si tienes 4 tipos de mensaje y 5 canales, ¿cuántas clases necesitarías SIN Bridge?


🌉 1. Un ejemplo sencillo: El mando a distancia universal

Imagina que tienes varios cacharros electrónicos (TV, Radio, Proyector) y quieres controlarlos con un Mando a Distancia Universal.

Así puedes crear un nuevo Mando (ej. un Mando con Voz) sin tocar los Dispositivos, y puedes crear un nuevo Dispositivo (ej. Proyector Laser) sin tocar los Mandos. Cada parte va a su rollo.

El truco está en que la Abstracción solo conoce la Interfaz del Implementador, nunca las clases concretas, eso es el puente.


🛠️ 2. Los roles del patrón Bridge

El Bridge tiene 4 piezas fundamentales:

Implementador (Interfaz)

Define los métodos básicos que todas las Implementaciones Concretas deben cumplir. Es el contrato de la parte de bajo nivel. Ej: SenderInterface con un método send().

Implementación Concreta

Las clases que hacen el trabajo sucio de verdad, siguiendo la interfaz del Implementador. Ej: EmailSender, SmsSender.

Abstracción

La clase de alto nivel que tiene la lógica de negocio. Guarda una referencia a la Interfaz del Implementador (ahí está el puente). Delega el curro real al implementador. Ej: Message.

Abstracción Refinada

Las variaciones de la Abstracción que añaden o cambian la lógica de negocio, pero tirando de la misma Implementación. Ej: SimpleMessage, WarningMessage.

El esquema queda así: Abstracción Refinada → hereda de → Abstracción → contiene referencia a → Implementador (Interfaz) ← implementan ← Implementaciones Concretas.

El cliente solo tiene que montar el puente inyectando la implementación que quiera en la abstracción, y listo.

En el Patrón Bridge, ¿qué rol contiene la referencia al Implementador?


✉️ 3. El ejemplo clásico: Mensajes y Canales de Envío

Vamos al lío. Tienes diferentes tipos de mensajes (simples, advertencias) y diferentes canales para enviarlos (Email, SMS). La idea es que la forma en que se prepara el mensaje sea totalmente independiente del canal que lo manda.

<?php

// 1. La Interfaz del Implementador (El contrato del 'cómo' se envía)
interface SenderInterface {
    public function send(string $subject, string $body): void;
}

// 2. Implementaciones Concretas (El 'cómo' específico)
class EmailSender implements SenderInterface {
    public function send(string $subject, string $body): void {
        echo "[EMAIL SENDER] Enviando..." . PHP_EOL;
        echo "   ASUNTO: $subject" . PHP_EOL;
        echo "   CUERPO: $body" . PHP_EOL;
    }
}

class SmsSender implements SenderInterface {
    public function send(string $subject, string $body): void {
        // En SMS el 'subject' se ignora o se une al cuerpo.
        echo "[SMS SENDER] Enviando al móvil: $subject - $body (Máx. 160 chars)" . PHP_EOL;
    }
}

// 3. La Abstracción (La lógica de 'qué' se prepara, contiene el puente)
abstract class Message {
    // La Abstracción guarda la referencia a la Implementación (El Puente)
    protected SenderInterface $sender;
    protected string $content;

    public function __construct(SenderInterface $sender, string $content) {
        $this->sender = $sender;
        $this->content = $content;
    }

    // Método que las Abstracciones Refinadas deben implementar
    abstract public function send(): void;
}

// 4. Abstracciones Refinadas (El 'qué' específico)
class SimpleMessage extends Message {
    public function send(): void {
        $this->sender->send("Mensaje Simple", $this->content);
    }
}

class WarningMessage extends Message {
    public function send(): void {
        $subject = "⚠️ ALERTA: Acceso Ilegal Detectado";
        $body = "Por favor, revisa: " . $this->content;
        $this->sender->send($subject, $body);
    }
}

// 5. Uso del patrón (El Cliente)
// ---------------------------------------------------------------------

$emailSender = new EmailSender();
$smsSender = new SmsSender();

// Se crea un "Mensaje Simple" enviado por Email
$simpleEmail = new SimpleMessage($emailSender, "Tu pedido 456 ha sido enviado.");
$simpleEmail->send();

echo PHP_EOL;

// Se crea un "Mensaje de Advertencia" enviado por SMS
$warningSms = new WarningMessage($smsSender, "Intento de login fallido desde IP sospechosa.");
$warningSms->send();

// ---------------------------------------------------------------------

// 🖥️ Salida del programa:
// [EMAIL SENDER] Enviando...
//    ASUNTO: Mensaje Simple
//    CUERPO: Tu pedido 456 ha sido enviado.
//
// [SMS SENDER] Enviando al móvil: ⚠️ ALERTA: Acceso Ilegal Detectado
// Por favor, revisa: Intento de login fallido desde IP sospechosa.

Fíjate que la Abstracción (Message) solo conoce SenderInterface. No tiene ni idea de si estás enviando por Email, SMS o paloma mensajera. Y las Implementaciones Concretas (EmailSender, SmsSender) no saben si están enviando un mensaje simple o una advertencia. Cada parte va a su rollo, y eso es lo que queremos.

Añadir un nuevo canal: WhatsApp

Ahora imagina que tu jefe te dice “oye, necesitamos enviar también por WhatsApp”. ¿Qué haces? Solo creas una nueva implementación concreta y ya:

class WhatsappSender implements SenderInterface {
    public function send(string $subject, string $body): void {
        echo "[WHATSAPP] 📱 $subject: $body" . PHP_EOL;
    }
}

// Y ya puedes usarlo con cualquier tipo de mensaje existente
$urgente = new WarningMessage(new WhatsappSender(), "Se ha detectado actividad sospechosa.");
$urgente->send();

Cero líneas modificadas en Message, SimpleMessage, WarningMessage, EmailSender o SmsSender. Eso es el poder del Bridge, nene.

Añadir un nuevo tipo de mensaje: Urgente

Y si ahora necesitas un mensaje urgente que repita el contenido y lo ponga en mayúsculas:

class UrgentMessage extends Message {
    public function send(): void {
        $subject = "🚨 URGENTE 🚨";
        $body = strtoupper($this->content) . " | " . strtoupper($this->content);
        $this->sender->send($subject, $body);
    }
}

// Funciona con TODOS los canales existentes automáticamente
$urgentEmail = new UrgentMessage(new EmailSender(), "Servidor caído");
$urgentSms = new UrgentMessage(new SmsSender(), "Servidor caído");
$urgentWhatsapp = new UrgentMessage(new WhatsappSender(), "Servidor caído");

Tampoco has tocado ninguna clase existente. Has añadido una Abstracción que funciona con todas las Implementaciones que ya tenías. Ambos ejes crecen de forma independiente, esa es la magia.

Para añadir un nuevo canal (ej. WhatsApp), ¿qué código existente hay que modificar?


🔀 4. Bridge vs Strategy vs Adapter

Estos tres patrones se parecen un huevo a primera vista y es muy fácil liarla. Los tres usan composición e interfaces, pero van a cosas distintas:

PatrónPropósitoCuándo usarlo
BridgeDesacoplar dos dimensiones que varían independientemente (abstracción + implementación)Cuando tienes N × M combinaciones que explotan en clases
StrategyIntercambiar un algoritmo en tiempo de ejecución sin cambiar el contextoCuando tienes un solo eje de variación (solo la implementación)
AdapterHacer que interfaces incompatibles trabajen juntasCuando integras código legacy o librerías externas con interfaces distintas

La diferencia clave entre Bridge y Strategy: en Strategy solo varía el “cómo” (el algoritmo), mientras que en Bridge varían ambos lados — el “qué” (la abstracción) y el “cómo” (la implementación). Si solo necesitas intercambiar el canal de envío pero el mensaje siempre es el mismo, con Strategy te apañas. Pero si el mensaje también varía (simple, advertencia, urgente…), entonces necesitas Bridge.

¿Qué diferencia principal hay entre Bridge y Strategy?


⚖️ 5. Relación con SOLID

OCP (Open/Closed)

Puedes meter nuevos canales o nuevos tipos de mensaje sin tocar el código que ya funciona. Es el principio que Bridge cumple más descaradamente.

SRP (Single Responsibility)

Cada clase tiene un solo motivo para cambiar. WarningMessage cambia si cambia la lógica del mensaje. EmailSender cambia si cambia el protocolo de envío. No se pisan.

DIP (Dependency Inversion)

La abstracción depende de la interfaz SenderInterface, no de EmailSender directamente. Las dependencias van hacia las abstracciones, no hacia los detalles concretos.

LSP (Liskov Substitution)

Cualquier SenderInterface puede sustituir a otra sin romper nada. WhatsappSender entra en lugar de EmailSender y todo sigue funcionando.

¿Qué principio SOLID cumple Bridge al permitir añadir canales sin modificar código existente?


✅ 6. Ventajas y desventajas

Ventajas
  • Adiós al producto cartesiano: N + M clases en vez de N × M. Menos clases, menos dolor.
  • OCP a tope: Nuevos canales o tipos de mensaje sin tocar nada de lo que ya funciona.
  • Desacoplamiento total: La abstracción y la implementación ni se conocen. Cada una a su rollo.
  • Testeable: Puedes inyectar mocks del Sender en los tests sin problemas.
  • Combinaciones en runtime: El cliente elige qué abstracción + implementación usar al instanciar.
Desventajas
  • Complejidad inicial: Muchas interfaces y clases para algo que al principio podría ser una sola clase. Se puede sentir como matar moscas a cañonazos.
  • Cuesta pillarlo: Bridge es de los patrones más puñeteros de entender al principio.
  • Over-engineering: Si solo un eje varía, con Strategy vas sobrado y te ahorras movida.

⚠️ 7. Errores comunes al implementar Bridge

1. Usar Bridge cuando solo varía un eje

Si tus mensajes siempre son del mismo tipo y solo varía el canal (Email, SMS, Push), no necesitas Bridge. Con Strategy es suficiente. Bridge brilla cuando ambos ejes cambian. Si solo cambia uno, estás metiendo complejidad gratis y complicándote la vida para nada.

2. Que la Abstracción conozca las implementaciones concretas

// ❌ MAL: la abstracción conoce la implementación concreta
class SimpleMessage extends Message {
    public function send(): void {
        if ($this->sender instanceof EmailSender) {
            // lógica específica para email...
        }
        $this->sender->send("Simple", $this->content);
    }
}

Si haces instanceof en la abstracción, has roto el puente. Se acabó. La abstracción solo debe hablar con la interfaz del implementador, nunca con las clases concretas. Si necesitas saber qué implementación tienes por debajo, algo has hecho mal.

¿Por qué es un error usar instanceof en la Abstracción para detectar la Implementación Concreta?

3. Interfaz del implementador demasiado ancha

Si tu SenderInterface tiene 15 métodos, algo huele mal. La interfaz tiene que ser lo más pequeña y cohesiva posible. Si necesitas métodos que no todos los canales implementan, seguramente tengas que partirla en interfaces más pequeñas (ISP de SOLID).

// ❌ Demasiados métodos, no todos los canales los necesitan
interface SenderInterface {
    public function send(string $subject, string $body): void;
    public function sendWithAttachment(string $subject, string $body, string $file): void;
    public function sendBulk(array $recipients, string $body): void;
    public function getDeliveryStatus(): string;
}

// ✅ Interfaz mínima y cohesiva
interface SenderInterface {
    public function send(string $subject, string $body): void;
}

4. No pasar la implementación por constructor

El puente se monta en el constructor. Si cambias la implementación después de crear el objeto, puedes acabar con estados raros e inconsistentes:

// ✅ El puente se construye al instanciar
$mensaje = new WarningMessage(new EmailSender(), "Alerta!");

// ❌ Evita setters para cambiar la implementación después
$mensaje->setSender(new SmsSender()); // Peligroso si ya se usó EmailSender

🤔 8. ¿Cuándo usar Bridge y cuándo no?

Úsalo cuando...
  • Tienes dos dimensiones de cambio que crecen independientemente
  • El producto cartesiano de clases se te va de las manos
  • Quieres poder cambiar la implementación en runtime
  • Necesitas que la abstracción y la implementación evolucionen por separado
Evítalo cuando...
  • Solo varía una dimensión (usa Strategy y déjate de historias)
  • Tienes pocas combinaciones y no van a crecer
  • La abstracción y la implementación están muy acopladas por naturaleza
  • Estás anticipando una complejidad que probablemente nunca llegue

Si te pillas creando clases con nombres tipo TipoACanal1, TipoACanal2, TipoBCanal1… eso es Bridge dándote gritos para que lo uses.

¿Cuándo NO deberías usar el Patrón Bridge?


💡 9. Conclusión

El Patrón Bridge te salva de la pesadilla del producto cartesiano. En vez de crear una clase por cada combinación de qué y cómo, montas un puente entre las dos dimensiones y dejas que cada una crezca a su ritmo.

La clave para pillarlo es que pienses en dos ejes. Si tu código tiene un eje que varía (tipos de mensaje) y otro eje que también varía (canales de envío), y no quieres que cada combinación sea una clase nueva, Bridge es tu patrón.

EA pues eso es todo, ¡saluditos y nos vemos en los bares! 🍻