7. Bridge
The Bridge pattern decouples an abstraction from its implementation so that the two can vary independently. It uses composition instead of inheritance to split a monolithic class hierarchy into two orthogonal dimensions.
Intent and Motivation
Intent: Decouple an abstraction from its implementation so that the two can vary independently.
Motivation: Imagine a remote control (abstraction) and devices like TVs and radios (implementations). A naive design creates TVRemote, RadioRemote, AdvancedTVRemote, AdvancedRadioRemote — exponential subclass explosion. Bridge extracts the implementation into a separate hierarchy (Device interface with TV, Radio) and the abstraction holds a reference to a Device. Now you can combine any remote with any device: BasicRemote + SonyTV, AdvancedRemote + Radio.
This is critical when both the abstraction and implementation need to evolve independently — rendering engines, database drivers, messaging transports.
Structure (UML-like)
┌───────────────────┐ has-a ┌───────────────────┐
│ Abstraction │ ───────────────────► │ Implementor │ (interface)
├───────────────────┤ ├───────────────────┤
│ - implementor │ │ + operationImpl() │
│ + operation() │ └─────────▲─────────┘
└─────────▲─────────┘ │
│ extends │ implements
┌─────────┴─────────┐ ┌────────┴────────┐
│RefinedAbstraction │ │ConcreteImplementor│
└───────────────────┘ └───────────────────┘
Participants:
- Abstraction — defines the high-level interface, delegates to Implementor.
- RefinedAbstraction — extended abstraction with additional behavior.
- Implementor — interface for implementation classes.
- ConcreteImplementor — platform-specific or variant-specific implementation.
Java Example
// Implementor
interface Device {
boolean isEnabled();
void enable();
void disable();
int getVolume();
void setVolume(int percent);
}
class TV implements Device {
private boolean on = false;
private int volume = 50;
public boolean isEnabled() { return on; }
public void enable() { on = true; System.out.println("TV on"); }
public void disable() { on = false; System.out.println("TV off"); }
public int getVolume() { return volume; }
public void setVolume(int v) { volume = v; System.out.println("TV volume: " + v); }
}
// Abstraction
abstract class RemoteControl {
protected Device device;
RemoteControl(Device device) { this.device = device; }
void togglePower() {
if (device.isEnabled()) device.disable();
else device.enable();
}
void volumeUp() { device.setVolume(device.getVolume() + 10); }
}
class BasicRemote extends RemoteControl {
BasicRemote(Device device) { super(device); }
}
class AdvancedRemote extends RemoteControl {
AdvancedRemote(Device device) { super(device); }
void mute() { device.setVolume(0); System.out.println("Muted"); }
}
// Usage — any remote works with any device
RemoteControl remote = new AdvancedRemote(new TV());
remote.togglePower();
remote.volumeUp();
((AdvancedRemote) remote).mute();
JavaScript Example
// Implementor — rendering backends
class CanvasRenderer {
drawCircle(x, y, r) {
console.log(`Canvas: circle at (${x},${y}) r=${r}`);
}
}
class SVGRenderer {
drawCircle(x, y, r) {
console.log(`SVG: <circle cx="${x}" cy="${y}" r="${r}"/>`);
}
}
// Abstraction
class Shape {
constructor(renderer) {
this.renderer = renderer;
}
}
class Circle extends Shape {
constructor(renderer, x, y, radius) {
super(renderer);
this.x = x; this.y = y; this.radius = radius;
}
draw() {
this.renderer.drawCircle(this.x, this.y, this.radius);
}
}
// Combine any shape with any renderer
new Circle(new CanvasRenderer(), 10, 20, 5).draw();
new Circle(new SVGRenderer(), 10, 20, 5).draw();
Real-World Use Cases
| Framework / System | Usage |
|---|---|
| JDBC | Connection (abstraction) bridges to vendor-specific drivers (implementor). |
| SLF4J | Logging facade bridges to Logback, Log4j, or JUL implementations. |
| AWT / Java2D | Platform-independent graphics API bridges to OS-native rendering. |
| React Native | JavaScript components bridge to native iOS/Android UI implementations. |
| AWS SDK v2 | Service clients (abstraction) bridge to HTTP client implementations (Netty, Apache). |
| Database abstraction layers | Sequelize, TypeORM — ORM API bridges to MySQL, PostgreSQL, SQLite drivers. |
Pros and Cons
| Pros | Cons |
|---|---|
| Platform independence — swap implementations at runtime or compile time | Increases complexity for simple, stable hierarchies |
| Hides implementation details from clients | Requires careful design upfront to identify orthogonal dimensions |
| Both abstraction and implementation can be extended independently | Indirection adds a layer that must be understood |
| Replaces fragile multi-dimensional inheritance with composition | Over-abstraction when only one implementation will ever exist |
| Follows Single Responsibility and Open/Closed principles | More classes and interfaces to maintain |
When to Use vs When NOT to Use
Use when:
- You want to avoid a permanent binding between abstraction and implementation.
- Both abstraction and implementation need to be extended by subclassing.
- Changes in implementation should not affect client code.
- You need to share an implementation among multiple objects (reference counting, connection pooling).
- You have a proliferation of classes from coupled abstraction-implementation inheritance.
Do NOT use when:
- Only one implementation will ever exist and is unlikely to change.
- The abstraction and implementation are tightly coupled by nature (no benefit from separation).
- A simple inheritance hierarchy with 2–3 variants is sufficient.
- You are retrofitting incompatible interfaces (use Adapter instead).
Common Mistakes
- Confusing Bridge with Adapter — Bridge is designed upfront; Adapter fixes existing incompatibility after the fact.
- Exposing the implementor to clients — clients should interact only with the abstraction.
- Too many abstraction levels — keep the hierarchy shallow; deep nesting obscures the pattern.
- Not injecting the implementor — hard-coding
new ConcreteImplementor()inside the abstraction defeats the purpose. - Using Bridge when Strategy suffices — if only the algorithm varies (not the entire implementation), Strategy is simpler.
Related Patterns
- Adapter — Bridge separates design-time concerns; Adapter makes existing classes work together.
- Strategy — similar composition structure; Strategy swaps algorithms, Bridge swaps platform implementations.
- Abstract Factory — can create and configure a Bridge’s implementor at creation time.
- State — structure resembles Bridge; State changes behavior based on internal state, Bridge delegates to implementor.
- Dependency Injection — modern DI containers wire Bridge abstractions to implementations via configuration.