Introduction
Sometimes a single concept in your system varies along two different dimensions:
Shapes:
Circle,Square,RectangleRendering APIs:
OpenGL,DirectX,Vulkan
If you model this with plain inheritance, you might end up with classes like:
OpenGLCircle,OpenGLSquare,DirectXCircle,DirectXSquare, and so on.
This leads to a class explosion: every new shape × every new rendering API = a new subclass.
The Bridge Design Pattern solves this by separating the abstraction from its implementation, letting them evolve independently and be combined flexibly at runtime.
Intent of the Bridge pattern
The core intent:
Decouple an abstraction from its implementation so that the two can vary independently.Key ideas:
Abstraction: the high‑level concept the client uses (e.g.,
Shape).Implementor: the low‑level detail that actually does the work (e.g.,
Renderer).The abstraction has a reference to an implementor (composition), instead of inheriting from it.
You can extend abstraction and implementation hierarchies separately without creating a subclass for every combination.
Bridge is about two orthogonal dimensions of variation, not just adapting mismatched interfaces as in Adapter.
Basic structure: Abstraction and Implementor
Conceptually:
Abstraction(e.g.,Shape)Holds a pointer/reference to
Implementor(e.g.,Renderer).Defines high‑level operations that delegate “how” to the implementor.
RefinedAbstractionConcrete specializations of the abstraction (e.g.,
Circle,Rectangle).
ImplementorInterface for low‑level operations (e.g.,
drawCircle,drawRectangle).
ConcreteImplementorConcrete implementations (e.g.,
OpenGLRenderer,DirectXRenderer).
C++ example: Shapes and Renderers
Step 1: Implementor hierarchy (how to draw)
Concrete implementors:
Step 2: Abstraction hierarchy (what it is)
Concrete abstraction:
Step 3: Using the bridge
Now:
Shapehierarchy varies independently (e.g., later addRectangle,Triangle).Rendererhierarchy also varies independently (e.g., addVulkanRenderer).No need for
OpenGLCircle,DirectXCircle,VulkanCircle, etc. You just combineCirclewith whicheverRendereryou need.
Why use Bridge instead of simple inheritance?
Without Bridge, a naive design might be:
class OpenGLCircleclass DirectXCircleclass OpenGLRectangleclass DirectXRectangle
Problems:
Adding a new shape requires implementing it for every renderer.
Adding a new renderer requires implementing it for every shape.
The number of classes grows as a product of the two dimensions.
With Bridge:
You have one
Circleclass that works with anyRenderer.To support a new renderer, just add one
ConcreteImplementor(e.g.,VulkanRenderer).To support a new shape, add one
RefinedAbstraction(e.g.,Rectangle) that uses the existingRendererinterface.
Bridge is especially helpful when:
You know in advance there are two independent axes of change.
Both axes are likely to grow over time.
Bridge vs Adapter
Bridge and Adapter can look similar (both use composition and interfaces), but their intent differs:
Adapter
Goal: Make two existing interfaces compatible without changing them.
Typically used to integrate legacy or third‑party code.
Often applied after the fact.
Bridge
Goal: Design a system with two independent dimensions of variation from the start.
Used proactively in your design to avoid class explosion.
Both abstraction and implementor are under your control.
A mental shortcut:
If you’re trying to plug in an incompatible library, think Adapter.
If you’re trying to separate your own abstraction from its varying implementations, think Bridge.
Another example: Remote control and devices
Imagine:
Abstraction hierarchy:
RemoteControl,AdvancedRemoteControl.Implementor hierarchy:
TV,Radio.
With Bridge:
RemoteControlholds aDevice&and forwards operations to it.You can pair any remote with any device at runtime:
TVwithAdvancedRemoteControl,Radiowith simpleRemoteControl, etc.Adding a
SmartTVorSpeakerdoesn’t require new remote subclasses.
This fits many Low Level Design problems where you have:
“Controller” vs “Controlled device”
“View” vs “Platform‑specific windowing system”
“Abstraction of operation” vs “Concrete backend implementation”
When to use the Bridge pattern (and when not to)
Use Bridge when:
A class has two independent reasons to change (two dimensions of variation).
You want to avoid a combinatorial explosion of subclasses.
You foresee adding more variations in both axes over time.
You want to choose or swap implementations at runtime (e.g., select renderer based on configuration).
Consider skipping Bridge when:
There is only one implementation and no real sign of multiple axes of variation.
The hierarchy is small and simple; Bridge might over‑engineer the solution.
You are actually solving an integration problem (then Adapter might be enough).
As always, apply KISS and YAGNI:
Start simple.
Introduce Bridge when both abstraction and implementation clearly need to vary independently.
Summary
The Bridge Design Pattern is a structural pattern that:
Separates an abstraction (e.g.,
Shape,RemoteControl) from its implementation (e.g.,Renderer,Device).Connects them via composition, allowing each side to have its own hierarchy.
Prevents class explosion when there are two independent dimensions of variation.
Differs from Adapter by being a proactive design choice for flexibility, not just a compatibility fix.
You saw:
A C++ example where
Shapeuses aRendererto draw circles.How adding new shapes or new renderers doesn’t multiply the number of classes.
How Bridge compares with Adapter and when to use each.