Skip to main content

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

  1. Spot two axes of variation in a class hierarchy. If you can only name one axis, Bridge is premature.
  2. Decide which axis is the what (caller-facing) and which is the how (internal).
  3. Extract the how into an interface and make the what hold a reference to it.
  4. Pass the implementation in through the constructor (this is also DI).
  5. Keep the abstraction thin; the implementation is where the real work lives.

Check Yourself

  1. What problem does Bridge solve that plain composition does not?
  2. Why is Bridge usually premature in a small application?
  3. 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 abstraction
  • Transport (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?

Read This Only If Stuck