20 minlesson

Introduction to Bridge Pattern

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:

typescript
1// Without Bridge - explosion of classes
2class 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):

typescript
1// Implementation interface (channel)
2interface NotificationChannel {
3 send(recipient: string, subject: string, body: string): Promise<void>;
4}
5
6// Concrete implementations
7class EmailChannel implements NotificationChannel {
8 async send(recipient: string, subject: string, body: string): Promise<void> {
9 // Send via email
10 }
11}
12
13class SMSChannel implements NotificationChannel {
14 async send(recipient: string, subject: string, body: string): Promise<void> {
15 // Send via SMS (body only, limited chars)
16 }
17}
18
19// Abstraction
20abstract class Notification {
21 constructor(protected channel: NotificationChannel) {}
22 abstract notify(recipient: string, data: NotificationData): Promise<void>;
23}
24
25// Refined abstractions
26class 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}
35
36class 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

  1. Abstraction - Defines the abstraction's interface, maintains reference to implementor
  2. RefinedAbstraction - Extends the abstraction interface
  3. Implementor - Defines the interface for implementation classes
  4. ConcreteImplementor - Implements the implementor interface

When to Use Bridge

  1. Two orthogonal dimensions - When you have variations in two independent aspects
  2. Avoid permanent binding - When abstraction and implementation should vary independently
  3. Both should be extensible - Adding new abstractions or implementations shouldn't affect the other
  4. Implementation details hidden - When you want to hide implementation from clients

Bridge in Logistics

Common logistics scenarios:

AbstractionImplementation
Notification TypeDelivery Channel
Report FormatOutput Destination
Shipment TypeCarrier Service
Label FormatPrinter Type

Summary

Bridge is essential when dealing with multiple dimensions of variation. It promotes composition over inheritance and makes systems more flexible and maintainable.