Saltar al contenidoJMRG
Todas las entradas

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 <|.. SMSChannel

Implementació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.