The Bridge Design Pattern is a structural pattern that lets you decouple an abstraction from its implementation, allowing the two to vary independently.
It’s particularly useful in situations where:
The Bridge Pattern splits a class into two separate hierarchies:
These two hierarchies are "bridged" via composition — not inheritance — allowing you to mix and match independently.
Let’s walk through a real-world example to see how we can apply the Bridge Pattern to build a system that is both flexible and scalable, without being buried under layers of rigid subclasses.
Imagine you're building a cross-platform graphics library. It supports rendering shapes like circles and rectangles using different rendering approaches:
Now, you need to support:
Circle
, Rectangle
)VectorRenderer
, RasterRenderer
)You might start by creating a class hierarchy that looks like this:
Every new combination of shape and rendering method requires a new subclass:
This makes the class hierarchy bloated and rigid.
Each class ties together shape logic and rendering logic. You can’t reuse rendering behavior independently of the shape — they’re intertwined.
If you want to support a new rendering engine, you must modify or recreate every shape for that renderer.
We need a solution that:
Shape
) from its implementation (Renderer
)This is exactly where the Bridge Pattern comes in.
The Bridge Design Pattern lets you split a class into two separate hierarchies — one for the abstraction and another for the implementation — so that they can evolve independently.
In the Bridge Pattern, "abstraction has-a implementation" — the abstraction delegates work to an implementor object.
Shape)
The high-level interface that defines the abstraction's core behavior. It maintains a reference to an Implementor
and delegates work to it.
Circle
, Rectangle)
A concrete subclass of Abstraction
that adds additional behaviors or logic. It still relies on the implementor for actual execution.
Renderer)
An interface that declares the operations to be implemented by concrete implementors. These are the low-level operations.
VectorRenderer,RasterRenderer)
Platform- or strategy-specific classes that implement the Implementor
interface. They contain the actual logic for performing the delegated operations.
Let’s now implement the Bridge Pattern to decouple our Shape abstraction (e.g., Circle
, Rectangle
) from the Renderer implementation (e.g., VectorRenderer
, RasterRenderer
).
This will allow us to mix and match shapes and rendering engines freely, without subclass explosion.
Renderer
)This interface declares rendering operations for shapes. Concrete implementations will define how to render shapes using a particular technique (vector, raster, etc.).
These classes provide platform-specific rendering logic.
Shape
)This class holds a reference to the renderer and defines a general draw()
method.
Each shape delegates rendering to the renderer passed into it.
Now we can freely combine shapes and rendering strategies — at runtime — without duplicating classes.