Patrón Bridge: Separando Abstracción de Implementación
design-patternsstructuralbridge

Intención
Bridge desacopla una abstracción de su implementación, permitiendo que ambas varíen independientemente. Evita la explosión combinatoria de clases cuando tienes múltiples dimensiones de variación.
El Problema
Imagina un sistema de notificaciones con múltiples canales y tipos de mensaje:
// Sin Bridge: explosión de clases
class EmailUrgentNotification { }
class EmailRegularNotification { }
class SMSUrgentNotification { }
class SMSRegularNotification { }
class PushUrgentNotification { }
class PushRegularNotification { }
// 2 tipos × 3 canales = 6 clases
// 4 tipos × 5 canales = 20 clases 😱La Solución: Bridge
classDiagram
class Notification {
<<abstract>>
#channel: NotificationChannel
+send(message: string)
}
class UrgentNotification {
+send(message: string)
}
class RegularNotification {
+send(message: string)
}
class NotificationChannel {
<<interface>>
+deliver(message: string, priority: string)
}
class EmailChannel {
+deliver(message: string, priority: string)
}
class SMSChannel {
+deliver(message: string, priority: string)
}
Notification <|-- UrgentNotification
Notification <|-- RegularNotification
Notification o--> NotificationChannel
NotificationChannel <|.. EmailChannel
NotificationChannel <|.. SMSChannelImplementación TypeScript
// Implementación (cómo entregar)
interface NotificationChannel {
deliver(message: string, priority: 'high' | 'normal'): Promise<void>;
}
class EmailChannel implements NotificationChannel {
constructor(private smtp: SMTPClient) {}
async deliver(message: string, priority: 'high' | 'normal'): Promise<void> {
await this.smtp.send({
body: message,
headers: { 'X-Priority': priority === 'high' ? '1' : '3' },
});
}
}
class SMSChannel implements NotificationChannel {
constructor(private twilioClient: TwilioClient) {}
async deliver(message: string, priority: 'high' | 'normal'): Promise<void> {
const truncated = message.slice(0, 160);
await this.twilioClient.sendSMS(truncated);
}
}
class PushChannel implements NotificationChannel {
async deliver(message: string, priority: 'high' | 'normal'): Promise<void> {
await firebase.messaging().send({
notification: { body: message },
android: { priority: priority === 'high' ? 'high' : 'normal' },
});
}
}
// Abstracción (qué enviar)
abstract class Notification {
constructor(protected channel: NotificationChannel) {}
abstract send(message: string): Promise<void>;
}
class UrgentNotification extends Notification {
async send(message: string): Promise<void> {
const formatted = `🚨 URGENT: ${message}`;
await this.channel.deliver(formatted, 'high');
}
}
class RegularNotification extends Notification {
async send(message: string): Promise<void> {
await this.channel.deliver(message, 'normal');
}
}
// Uso: Combinar cualquier tipo con cualquier canal
const urgentEmail = new UrgentNotification(new EmailChannel(smtpClient));
const regularSMS = new RegularNotification(new SMSChannel(twilioClient));
const urgentPush = new UrgentNotification(new PushChannel());
await urgentEmail.send('Server is down!');
await regularSMS.send('Your order shipped');Cuándo Usar
| Situación | Recomendación |
|---|---|
| Múltiples dimensiones de variación | ✅ Recomendado |
| Evitar explosión de subclases | ✅ Recomendado |
| Cambiar implementación en runtime | ✅ Recomendado |
| Una sola dimensión de variación | ❌ Herencia simple |
Conclusión
Bridge es esencial cuando tienes dos ejes ortogonales de variación y quieres combinarlos libremente.