What It Is
The Bridge pattern splits one concept into two independent dimensions so they can vary separately. Shapes and ways of drawing them; messages and ways of sending them.
Without the bridge you get one class per combination — VectorCircle, TextCircle, VectorSquare... — and the list multiplies. With the bridge, one side holds the other as a field: any shape works with any renderer.
Example: Shapes and Renderers
2 renderers × 2 shapes would be 4 combined classes; here it is 2 + 2, connected by one protocol field — the bridge.
Any shape, any renderer
The same circle draws itself two ways just by holding a different renderer. Adding a renderer or a shape adds one class, never a whole row of combinations.
Output: complete Python program
from dataclasses import dataclass
from typing import Protocol
# One dimension: HOW to draw.
class Renderer(Protocol):
def draw(self, name: str) -> str: ...
class VectorRenderer:
def draw(self, name: str) -> str:
return "drawing " + name + " with smooth vector lines"
class TextRenderer:
def draw(self, name: str) -> str:
return "drawing " + name + " with text characters"
# Other dimension: WHAT to draw. The renderer field is the bridge.
@dataclass
class Shape:
name: str
renderer: Renderer
def draw(self) -> str:
return self.renderer.draw(self.name)
shapes = [
Shape("circle", VectorRenderer()),
Shape("circle", TextRenderer()),
Shape("square", VectorRenderer()),
]
for s in shapes:
print(s.draw())
When to Use It
Use it when you notice combination explosion: every new kind on one axis forces new classes on the other. Two independent axes — split them.
Skip it when one dimension has exactly one implementation. A bridge to a single shore is just a pier.
Pros and Cons
Pros: the two sides grow independently; combinations come free; each side is testable alone.
Cons: more indirection up front; recognizing the two axes early is genuinely hard — splitting too early creates abstraction nobody needs.