Three shapes. Three ways to draw them — SVG, canvas, ASCII.
Model both with inheritance and you get SvgCircle, CanvasCircle,
AsciiCircle, SvgSquare, and so on. Nine classes, each mostly a copy of two
others.
new Circle(new SVGRenderer())
classes you must write: 6
3 + 3. A fourth shape adds one class; a fourth renderer adds one class. The two axes stopped multiplying because a shape now holds a renderer instead of inheriting from one.
A fourth renderer adds three classes. A fourth shape adds three more. The count is the product, and the duplication grows with it.
The realisation
There are two independent things varying: what the shape is, and how it is drawn. Inheritance can only express one of them, so the second one gets forced into the same hierarchy and multiplies it.
The pattern
Separate them, and give one a reference to the other.
/** The implementation axis: drawing primitives, no shapes. */interface Renderer { drawCircle(x: number, y: number, r: number): void; drawLine(x1: number, y1: number, x2: number, y2: number): void;}
/** The abstraction axis: shapes, no drawing. */abstract class Shape { constructor(protected readonly renderer: Renderer) {} // ← the bridge abstract draw(): void;}
class Circle extends Shape { constructor(renderer: Renderer, private readonly r: number) { super(renderer); } draw() { this.renderer.drawCircle(0, 0, this.r); }}Three shapes plus three renderers: six classes, and every one of the nine combinations is available.
new Circle(new SvgRenderer(), 10);new Circle(new AsciiRenderer(), 10);Recognising it
The tell is a class name with two adjectives in it: SvgCircle,
MySqlUserRepository, EncryptedFileTransport, WindowsDarkButton.
Two adjectives usually means two axes fused into one hierarchy. If both adjective sets can grow independently, you want a bridge.
Where it costs more than it saves
The abstraction can only use what the implementor interface offers. Designing that interface is the hard part: too narrow and shapes cannot draw what they need, too wide and every renderer must implement operations only one shape uses.
That interface tends to drift toward the union of what all shapes want, which is the failure mode — at which point you have a renderer interface with twenty methods and each renderer implements twelve of them.
Where it already is
JDBC — Connection is the abstraction, the driver is the implementor. Any
cross-platform toolkit separating widgets from a native peer. A logging facade
over multiple backends. And, at the largest scale, a device driver model: the
kernel’s abstraction on one side, hardware-specific code on the other.