Introduction to Bridge Pattern
Bridge decouples an abstraction from its implementation so that the two can vary independently. It's particularly useful when you have multiple dimensions of variation.
The Problem
Consider a notification system with multiple types and channels:
typescript1// Without Bridge - explosion of classes2class EmailShippedNotification { }3class EmailDeliveredNotification { }4class EmailExceptionNotification { }5class SMSShippedNotification { }6class SMSDeliveredNotification { }7class SMSExceptionNotification { }8class PushShippedNotification { }9class PushDeliveredNotification { }10class PushExceptionNotification { }11// 3 types x 3 channels = 9 classes!12// Add a new channel? 3 more classes.13// Add a new type? 3 more classes.
The Solution: Bridge
Separate the abstraction (notification type) from implementation (delivery channel):
typescript1// Implementation interface (channel)2interface NotificationChannel {3 send(recipient: string, subject: string, body: string): Promise<void>;4}56// Concrete implementations7class EmailChannel implements NotificationChannel {8 async send(recipient: string, subject: string, body: string): Promise<void> {9 // Send via email10 }11}1213class SMSChannel implements NotificationChannel {14 async send(recipient: string, subject: string, body: string): Promise<void> {15 // Send via SMS (body only, limited chars)16 }17}1819// Abstraction20abstract class Notification {21 constructor(protected channel: NotificationChannel) {}22 abstract notify(recipient: string, data: NotificationData): Promise<void>;23}2425// Refined abstractions26class ShippedNotification extends Notification {27 async notify(recipient: string, data: NotificationData): Promise<void> {28 await this.channel.send(29 recipient,30 `Package Shipped: ${data.trackingNumber}`,31 `Your package is on its way! Track at: ${data.trackingUrl}`32 );33 }34}3536class DeliveredNotification extends Notification {37 async notify(recipient: string, data: NotificationData): Promise<void> {38 await this.channel.send(39 recipient,40 `Package Delivered: ${data.trackingNumber}`,41 `Your package was delivered at ${data.deliveryTime}`42 );43 }44}
Pattern Structure
1┌─────────────────────┐ ┌─────────────────────┐2│ Abstraction │────────►│ Implementation │3│─────────────────────│ │ (interface) │4│ - impl: Impl │ └──────────┬──────────┘5│ + operation() │ │6└──────────┬──────────┘ │7 │ ┌──────────┴──────────┐8 │ │ │9┌──────────▼──────────┐ ┌──────▼──────┐ ┌───────▼───────┐10│ RefinedAbstraction │ │ ConcreteA │ │ ConcreteB │11│─────────────────────│ │─────────────│ │───────────────│12│ + operation() │ │ + operImpl()│ │ + operImpl() │13└─────────────────────┘ └─────────────┘ └───────────────┘
Key Participants
- Abstraction - Defines the abstraction's interface, maintains reference to implementor
- RefinedAbstraction - Extends the abstraction interface
- Implementor - Defines the interface for implementation classes
- ConcreteImplementor - Implements the implementor interface
When to Use Bridge
- Two orthogonal dimensions - When you have variations in two independent aspects
- Avoid permanent binding - When abstraction and implementation should vary independently
- Both should be extensible - Adding new abstractions or implementations shouldn't affect the other
- Implementation details hidden - When you want to hide implementation from clients
Bridge in Logistics
Common logistics scenarios:
| Abstraction | Implementation |
|---|---|
| Notification Type | Delivery Channel |
| Report Format | Output Destination |
| Shipment Type | Carrier Service |
| Label Format | Printer Type |
Summary
Bridge is essential when dealing with multiple dimensions of variation. It promotes composition over inheritance and makes systems more flexible and maintainable.