Saltar al contenido
DevPedia

Patrones estructuralesGuía 10 de 28

Bridge

Cómo separar una abstracción de su implementación para que ambas puedan evolucionar sin crear una subclase por cada combinación.

Actualizado 7 min de lectura

Bridge es un patrón estructural que separa una abstracción de su implementación —acá, el tipo de notificación y el canal de entrega— para que ambas puedan evolucionar por separado.

El problema

AndesShop envía distintos tipos de notificación —confirmación de compra, aviso de despacho, oferta promocional— y cada una puede entregarse por distintos canales —email, SMS, WhatsApp—. Modelar esto con herencia directa (OrderConfirmationEmail, OrderConfirmationSms, ShippingUpdateEmail, ShippingUpdateSms, ShippingUpdateWhatsapp…) crece de forma combinatoria: cada tipo de notificación nuevo se multiplica por cada canal existente, y cada canal nuevo, por cada tipo de notificación existente.

La solución

Bridge separa las dos dimensiones que varían — el tipo de notificación y el canal de entrega — en dos jerarquías de clases independientes, conectadas por una referencia (el “puente”). La jerarquía de notificaciones delega el envío efectivo a un objeto de canal, en vez de implementar el envío ella misma.

Estructura de Bridge: dos jerarquías independientes unidas por una referencia. Sumar un canal no toca las notificaciones y viceversa.

Ejemplo en Java

// La implementación: los distintos canales de entrega
interface DeliveryChannel {
    void deliver(String message);
}

class EmailChannel implements DeliveryChannel {
    public void deliver(String message) {
        System.out.println("Email: " + message);
    }
}

class SmsChannel implements DeliveryChannel {
    public void deliver(String message) {
        System.out.println("SMS: " + message);
    }
}

class WhatsappChannel implements DeliveryChannel {
    public void deliver(String message) {
        System.out.println("WhatsApp: " + message);
    }
}

// La abstracción: los tipos de notificación, que delegan el envío al canal
abstract class Notification {
    protected final DeliveryChannel channel;

    protected Notification(DeliveryChannel channel) {
        this.channel = channel;
    }

    public abstract void send();
}

class OrderConfirmation extends Notification {
    private final String orderId;

    public OrderConfirmation(DeliveryChannel channel, String orderId) {
        super(channel);
        this.orderId = orderId;
    }

    @Override
    public void send() {
        channel.deliver("Tu pedido " + orderId + " fue confirmado");
    }
}

class ShippingUpdate extends Notification {
    private final String trackingCode;

    public ShippingUpdate(DeliveryChannel channel, String trackingCode) {
        super(channel);
        this.trackingCode = trackingCode;
    }

    @Override
    public void send() {
        channel.deliver("Tu pedido está en camino. Seguimiento: " + trackingCode);
    }
}
// Cualquier tipo de notificación con cualquier canal, sin multiplicar clases:
// 2 tipos x 3 canales son 5 clases, no 6. Con 5 tipos y 4 canales serían 9, no 20.
new OrderConfirmation(new SmsChannel(), "AND-1042").send();
new ShippingUpdate(new WhatsappChannel(), "TRK-88192").send();

Cuándo usarlo

  • Cuando tenés dos dimensiones independientes que varían (tipo y canal, forma y plataforma, contenido y formato de salida) y notás que la herencia directa está generando una jerarquía de clases que crece multiplicativamente.
  • Cuando querés poder cambiar la implementación en tiempo de ejecución sin recompilar ni tocar la abstracción.

Cuándo evitarlo

Una sola dimensión que varía (por ejemplo, notificaciones siempre por email, sin otros canales previstos) no justifica Bridge: la indirección que agrega no se llega a aprovechar.

Ventajas y desventajas

VentajasDesventajas
Permite que abstracción e implementación evolucionen por separadoIntroduce una indirección adicional que puede complicar la lectura del código
Evita el crecimiento combinatorio de subclases: canal y tipo viven en jerarquías distintas, unidas por una referenciaPuede ser una complejidad innecesaria si solo hay una implementación real
Facilita cambiar la implementación en tiempo de ejecución

Relación con otros patrones

  • Se suele diseñar de entrada, a diferencia de Adapter, que se aplica después, sobre interfaces que ya existían por separado.
  • Puede combinarse con Abstract Factory: el factory decide qué implementación concreta conectar al puente.

Compartir esta guía

Buscá por concepto, patrón o práctica.