Introduction
The Bridge pattern separates an abstraction from its implementation so they can vary independently. It's useful when both can have multiple variations.
Key Concepts
Abstraction: The high-level control layer (e.g., RemoteControl).
Implementation: The low-level platform-specific code (e.g., TV, Radio).
Decoupling: Abstraction and implementation vary independently through composition, not inheritance.
Real World Context
Cross-platform UI frameworks use Bridge to separate widget logic from platform rendering (e.g., React Native's bridge to iOS/Android). Database drivers use Bridge—your ORM logic is the abstraction, the PostgreSQL/MySQL driver is the implementation.
Deep Dive
Device Remote Control
The abstraction (RemoteControl) and implementation (TV, Radio) vary independently. Any remote can control any device through the shared interface:
javascript// Implementation class TV { on() { console.log('TV on'); } off() { console.log('TV off'); } setVolume(v) { console.log(`TV volume: ${v}`); } } class Radio { on() { console.log('Radio on'); } off() { console.log('Radio off'); } setVolume(v) { console.log(`Radio volume: ${v}`); } } // Abstraction class RemoteControl { constructor(device) { this.device = device; } togglePower() { this.isOn ? this.device.off() : this.device.on(); this.isOn = !this.isOn; } } class AdvancedRemote extends RemoteControl { mute() { this.device.setVolume(0); } } // Use any remote with any device const tvRemote = new AdvancedRemote(new TV()); const radioRemote = new RemoteControl(new Radio());
Adding a new device (e.g., Speaker) requires only a new implementation class. Adding a new remote type (e.g., VoiceRemote) requires only a new abstraction subclass. Neither side affects the other.
Common Pitfalls
- Confusing with Adapter — Adapter makes existing incompatible interfaces work together; Bridge is designed upfront to separate abstraction from implementation.
- Over-abstraction — If you only have one implementation, Bridge adds unnecessary complexity.
- Leaking implementation details — The abstraction should not expose implementation-specific methods.
Best Practices
- Identify independent dimensions of variation — Bridge is valuable when both the abstraction and implementation have multiple variants.
- Inject the implementation — Pass the implementation to the abstraction's constructor (dependency injection).
- Define a clear implementation interface — All implementations must satisfy the same contract.
Summary
Bridge separates abstraction from implementation. Both can vary independently. Use when you have multiple dimensions of variation.
Code Examples
// Implementation interface
class TV {
on() { console.log('TV on'); }
off() { console.log('TV off'); }
setVolume(v) { console.log(`TV volume: ${v}`); }
}
class Radio {
on() { console.log('Radio on'); }
off() { console.log('Radio off'); }
setVolume(v) { console.log(`Radio volume: ${v}`); }
}
// Abstraction — takes any device implementation
class RemoteControl {
constructor(device) { this.device = device; }
togglePower() { this.isOn ? this.device.off() : this.device.on(); this.isOn = !this.isOn; }
}
class AdvancedRemote extends RemoteControl {
mute() { this.device.setVolume(0); }
}
new AdvancedRemote(new TV()).mute(); // "TV volume: 0"