15 minlesson

Bridge vs Adapter Pattern

Bridge vs Adapter Pattern

Bridge and Adapter look similar but solve different problems. Understanding when to use each is crucial.

Key Differences

AspectAdapterBridge
PurposeMake incompatible interfaces work togetherSeparate abstraction from implementation
When designedAfter classes existBefore classes exist
FocusInterface compatibilityExtensibility
RelationshipWraps existing codeDesigned from scratch

Adapter: Retrofitting

Adapter is reactive - you have existing code that doesn't fit:

typescript
1// Existing third-party API (can't change)
2class FedExTracker {
3 getStatus(trackingId: string): FedExStatus {
4 return { code: 'IN_TRANSIT', location: 'Memphis, TN' };
5 }
6}
7
8// Your interface (established in your codebase)
9interface TrackingService {
10 track(id: string): TrackingInfo;
11}
12
13// Adapter bridges the gap
14class FedExTrackingAdapter implements TrackingService {
15 constructor(private fedex: FedExTracker) {}
16
17 track(id: string): TrackingInfo {
18 const status = this.fedex.getStatus(id);
19 return {
20 status: this.mapStatus(status.code),
21 location: status.location
22 };
23 }
24
25 private mapStatus(code: string): string {
26 const map: Record<string, string> = {
27 'IN_TRANSIT': 'In Transit',
28 'DELIVERED': 'Delivered'
29 };
30 return map[code] ?? 'Unknown';
31 }
32}

Bridge: Planning Ahead

Bridge is proactive - you design for variation upfront:

typescript
1// You know you'll have multiple notification types AND channels
2// Design the bridge from the start
3
4interface NotificationChannel {
5 send(to: string, message: Message): Promise<void>;
6}
7
8abstract class Notification {
9 constructor(protected channel: NotificationChannel) {}
10 abstract prepare(data: EventData): Message;
11
12 async send(recipient: string, data: EventData): Promise<void> {
13 const message = this.prepare(data);
14 await this.channel.send(recipient, message);
15 }
16}
17
18// Now you can freely add types and channels
19class ShipmentNotification extends Notification { }
20class DelayNotification extends Notification { }
21
22class EmailChannel implements NotificationChannel { }
23class SMSChannel implements NotificationChannel { }
24class PushChannel implements NotificationChannel { }

Visual Comparison

Adapter (wrapping):

1┌──────────┐ ┌───────────┐ ┌──────────┐
2│ Client │─────►│ Adapter │─────►│ Adaptee │
3└──────────┘ └───────────┘ └──────────┘
4 (converts) (existing)

Bridge (separating):

1┌─────────────────┐ ┌─────────────────┐
2│ Abstraction │────────►│ Implementor │
3└────────┬────────┘ └────────┬────────┘
4 │ │
5 ┌────┴────┐ ┌────┴────┐
6 │ │ │ │
7┌───▼───┐ ┌───▼───┐ ┌───▼───┐ ┌───▼───┐
8│ Ref A │ │ Ref B │ │Impl 1 │ │Impl 2 │
9└───────┘ └───────┘ └───────┘ └───────┘

Decision Guide

Use Adapter when:

  • Integrating with existing, unchangeable code
  • Working with third-party libraries
  • Making legacy code fit new interfaces
  • You need a one-to-one interface mapping

Use Bridge when:

  • Designing a new system with multiple dimensions
  • Both abstraction and implementation will vary
  • You want to avoid class explosion from inheritance
  • Runtime binding between abstraction and implementation

Combined Example

Sometimes you need both patterns:

typescript
1// Third-party SMS provider (adapt it)
2class TwilioSDK {
3 sendMessage(to: string, from: string, text: string): void { }
4}
5
6// Adapter for Twilio
7class TwilioAdapter implements NotificationChannel {
8 constructor(private twilio: TwilioSDK, private fromNumber: string) {}
9
10 async send(to: string, message: Message): Promise<void> {
11 this.twilio.sendMessage(to, this.fromNumber, message.body);
12 }
13}
14
15// Use the adapted channel in the Bridge
16const smsChannel = new TwilioAdapter(new TwilioSDK(), '+1234567890');
17const notification = new ShipmentNotification(smsChannel);
18
19// Bridge lets you swap channels
20// Adapter lets you use Twilio with your channel interface

Summary

  • Adapter: "Make this existing thing fit my interface"
  • Bridge: "Design so I can mix and match dimensions"

Both promote loose coupling, but Adapter fixes compatibility issues while Bridge prevents them by design.