Bridge
What This Concept Is
Bridge is a structural pattern that separates an abstraction from its implementation, so both can vary independently. Concretely, you split one class hierarchy into two:
- an abstraction hierarchy (
Shape,RefinedShape, ...) that defines what the client sees - an implementation hierarchy (
Renderer,RasterRenderer,VectorRenderer, ...) that defines how it is actually done - the abstraction holds a reference to the implementation and delegates the "how"
Without Bridge you get a cross-product hierarchy: RasterCircle, VectorCircle, RasterSquare, VectorSquare, ... which doubles every time a new dimension appears. Bridge converts that multiplicative cost into additive cost.
The problem it absorbs: two dimensions of variation that would otherwise collide in a single class hierarchy.
Why It Matters Here
Bridge is the canonical answer to "combinatorial explosion in class hierarchies." You rarely reach for it in small code, but once a system has two genuinely orthogonal axes of variation -- platform and feature, message type and transport, shape and renderer -- the Bridge quietly keeps the codebase from doubling.
It is also the pattern most often confused with Adapter and Strategy. The difference is intent: Bridge decouples by design from the start; Adapter reconciles after the fact; Strategy swaps one algorithm within one abstraction.
Concrete Example
# Implementation hierarchy ("how")
class Renderer: pass
class RasterRenderer(Renderer):
def render_circle(self, x, y, r): print(f"pixels for circle at {x},{y} r={r}")
class VectorRenderer(Renderer):
def render_circle(self, x, y, r): print(f"<circle cx={x} cy={y} r={r}/>")
# Abstraction hierarchy ("what")
class Shape:
def __init__(self, renderer: Renderer):
self.renderer = renderer # the bridge
def draw(self): raise NotImplementedError
class Circle(Shape):
def __init__(self, renderer, x, y, r):
super().__init__(renderer)
self.x, self.y, self.r = x, y, r
def draw(self):
self.renderer.render_circle(self.x, self.y, self.r)
Circle(RasterRenderer(), 10, 20, 5).draw()
Circle(VectorRenderer(), 10, 20, 5).draw()
Structural sketch:
+-------------+ impl +------------------+
| Abstraction |---------->| Implementation |
+-------------+ +------------------+
^ ^
| |
+-----------+ +-----------------+
| Refined | | Concrete Impl |
| Abstract | | (Raster/Vector) |
+-----------+ +-----------------+
Two hierarchies evolve independently.
Common Confusion / Misconception
- Bridge vs Adapter. Bridge is designed up-front to decouple two dimensions; Adapter fixes a mismatch that was not planned. Same shape, different history and different intent.
- Bridge vs Strategy. Strategy swaps one algorithm inside one class. Bridge separates an entire abstraction from its entire implementation hierarchy.
- Bridge is not "just composition." It is composition with a second hierarchy of implementations that you expect to extend.
How To Use It
- Spot two axes of variation in a class hierarchy. If you can only name one axis, Bridge is premature.
- Decide which axis is the what (caller-facing) and which is the how (internal).
- Extract the how into an interface and make the what hold a reference to it.
- Pass the implementation in through the constructor (this is also DI).
- Keep the abstraction thin; the implementation is where the real work lives.
Check Yourself
- What problem does Bridge solve that plain composition does not?
- Why is Bridge usually premature in a small application?
- What tells you a would-be Bridge is actually an Adapter?
Mini Drill or Application
Sketch a Bridge between:
Notification(email, SMS, push) as the abstractionTransport(HTTP, SMTP, in-memory fake) as the implementation
Then write a one-paragraph critique of the design: is the second hierarchy earning its keep, or would two plain classes with a callable send function be enough?