LESSON 5.2
Bridge
Decouple an abstraction from its implementation so that the two can vary independently.
15 min read
Real-world analogy
A universal television remote control operates across multiple entertainment devices (Sony, Samsung, LG). The remote's control interface (power toggle, volume slider, channel stepper) represents the high-level abstraction. The infrared or Bluetooth signal transmission hardware and vendor-specific protocol represent the low-level implementation. Instead of manufacturing a separate physical remote for every single TV model and brand combination, the remote connects to the device protocol across a bridge.
The problem
You are building a cross-platform graphics rendering engine. You need to support basic geometric shapes (Circle, Square) across multiple rendering backends (VectorRenderer for SVG export, RasterRenderer for bitmap pixel screens).
If you use pure class inheritance, you must create a concrete class for every shape and renderer combination:
Shape
├── VectorCircle
├── RasterCircle
├── VectorSquare
└── RasterSquareAdding a third shape (Triangle) requires two new classes. Adding a third renderer (DirectXRenderer) requires three new classes. For $N$ shapes and $M$ renderers, you must maintain $N \times M$ classes.
This geometric class proliferation violates the Single Responsibility Principle by tangling domain geometry with platform-specific drawing routines.
The solution
The Bridge pattern splits a monolithic inheritance hierarchy into two independent, orthogonal hierarchies:
- Abstraction: High-level control layer (
Shape,RefinedAbstractionlikeCircle). - Implementation: Low-level platform layer (
Renderer,ConcreteImplementorlikeVectorRenderer).
The Abstraction holds a reference (the "bridge") to an Implementor interface and delegates rendering operations to it.
- Abstraction (
Shape): Defines high-level shape operations and retains a reference toRenderer. - Refined Abstraction (
Circle,Square): ExtendsShapewith domain-specific shape logic. - Implementor (
Renderer): Declares low-level drawing primitives (renderCircle,renderSquare). - Concrete Implementors (
VectorRenderer,RasterRenderer): Implements the platform-specific drawing primitives.
Step-by-step implementation
Step 1: Define the implementor interface
Declare the low-level primitive operations that concrete platforms must satisfy.
class Renderer {
renderCircle(radius) {
throw new Error("renderCircle() must be implemented");
}
renderSquare(side) {
throw new Error("renderSquare() must be implemented");
}
}public class Example {
interface Renderer
Step 2: Implement concrete platforms
Provide platform-specific implementations of the rendering primitives.
Step 3: Create the abstraction and refined shapes
The abstraction wraps the Renderer bridge reference. Refined shapes invoke the bridge.
The complete example
Here is the complete runnable implementation demonstrating shapes decoupled from renderers.
VectorRenderer: <circle r="10" />
RasterRenderer: Drawing square raster grid side 25
RasterRenderer: Drawing circle pixels with radius 10Variations
Abstraction × implementation (avoiding class explosion)
The primary reason to employ Bridge is avoiding exponential class proliferation:
- Without Bridge: Adding $N$ abstractions across $M$ platforms requires $N \times M$ concrete classes.
- With Bridge: Adding $N$ abstractions across $M$ platforms requires only $N + M$ classes.
For example, supporting 10 shapes across 4 renderers without Bridge requires 40 classes. With Bridge, it requires 10 shape classes and 4 renderer classes (14 classes total).
Remotes × devices
Another classic variation is device hardware control:
- Abstractions:
BasicRemote,AdvancedRemote(with mute and channel history). - Implementations:
SonyTV,RokuStreamer,AppleTV.
The remote abstraction provides high-level user commands, delegating low-level power and frequency modulation calls across the bridge to the device driver.
Where you've already used it
- Java Database Connectivity (JDBC):
java.sql.ConnectionandStatementact as the high-level Abstraction. Concrete database drivers (PostgreSQL Driver, MySQL Connector) act as the Implementors. - Java AWT Peers: GUI components (
Button,Window) serve as abstractions delegating drawing and events to OS-specific peer implementations (X11ButtonPeer,WindowsButtonPeer). - SLF4J: Simple Logging Facade for Java exposes logging abstractions (
Logger), bridging to concrete logging engines (Logback,Log4j2). - Cross-Platform UI Frameworks: Flutter and React Native separate component layout abstractions from platform rendering pipelines on iOS and Android.
Pros and cons
Advantages
- Decoupled Evolution: Abstraction and platform implementations can be extended independently without recompiling the other hierarchy.
- Eliminates Class Explosion: Replaces $N \times M$ class explosion with $N + M$ linear growth.
- Open/Closed Principle: New abstractions and new platforms are added without modifying existing code.
- Runtime Binding: Abstractions can switch their underlying implementor dynamically at runtime.
Disadvantages
- Increased Complexity: Introduces multiple interfaces and indirection layers into codebases that might only ever have one platform.
- Initial Design Overhead: Requires anticipating orthogonal dimensions upfront during architecture planning.
When not to use it
Do not apply Bridge if your domain will only ever have a single, static implementation. If you have Circle and it will only ever render to an HTML5 <canvas>, introducing a Renderer implementor hierarchy is premature overengineering.
Related patterns
| Pattern | Purpose | When Decided |
|---|---|---|
| Bridge | Decouples abstraction and implementation independently | Upfront architecture design |
| Adapter | Connects incompatible existing interfaces | After systems are already built |
| Strategy | Swaps interchangeable algorithms dynamically | Focuses on behavioral algorithms, not structural hierarchies |
| Abstract Factory | Creates families of matching products | Can be used alongside Bridge to instantiate matching implementors |
Interview questions
What is the core problem solved by the Bridge pattern? Bridge prevents a Cartesian product class explosion ($N \times M$) by decoupling an abstraction from its implementation via composition, scaling linearly as $N + M$.
How does Bridge differ from Strategy? While both use composition, Bridge is a structural pattern separating an abstraction hierarchy from an implementation hierarchy. Strategy is a behavioral pattern swapping interchangeable algorithms on a single context class.
Can the Implementor in a Bridge pattern be swapped at runtime? Yes. Because the bridge is maintained via an object reference on the abstraction, the implementor can be swapped dynamically (for example, switching from a VectorRenderer to a RasterRenderer during low-power mode).
How does JDBC illustrate the Bridge pattern?
JDBC client code interacts with standard java.sql interfaces (Abstraction). Concrete JDBC vendor drivers implement these interfaces for Postgres, MySQL, or Oracle (Implementor), allowing application code to remain database-agnostic.
Try it
Logging Framework LLD
Compose structured logging, redaction and bounded asynchronous output.