Skip to content

Bridge

Bridge decouples an abstraction from its implementation by putting each in its own class hierarchy, so the two can change and grow independently instead of multiplying together.

Suppose you have shapes — circle, square — and you want to draw each one with different rendering backends, say a vector renderer and a raster renderer. The naive route is inheritance: VectorCircle, RasterCircle, VectorSquare, RasterSquare. Add a third shape or a third backend and the class count explodes as the product of the two dimensions. Worse, every shape now knows about every backend.

Bridge breaks that product apart. One hierarchy models the abstraction (the shapes and their high-level operations); a separate hierarchy models the implementation (the rendering primitives). The abstraction holds a reference to an implementation object and delegates the low-level work to it. Now shapes and renderers vary on independent axes: three shapes plus two renderers means five classes, not six, and adding either side does not touch the other.

classDiagram
  class Shape {
    <<abstract>>
    #renderer: Renderer
    +draw()
  }
  class Circle {
    +draw()
  }
  class Square {
    +draw()
  }
  class Renderer {
    <<interface>>
    +drawCircle(r)
    +drawRectangle(w, h)
  }
  class VectorRenderer
  class RasterRenderer
  Shape <|-- Circle
  Shape <|-- Square
  Shape o--> Renderer : delegates to
  Renderer <|.. VectorRenderer
  Renderer <|.. RasterRenderer
The shape hierarchy bridges to a separate renderer hierarchy
  • Abstraction (Shape) — the high-level interface clients use. It holds a reference to an implementor and delegates primitive operations to it.
  • Refined Abstraction (Circle, Square) — concrete variants of the abstraction that may add their own operations.
  • Implementor (Renderer) — the interface for the low-level operations the abstraction relies on.
  • Concrete Implementor (VectorRenderer, RasterRenderer) — the actual implementations the abstraction can be paired with at runtime.

Shapes delegate their drawing to a pluggable renderer. Any shape works with any renderer, chosen when the shape is created.

// Implementor: the low-level drawing operations.
interface Renderer {
drawCircle(radius: number): string;
drawRectangle(width: number, height: number): string;
}
class VectorRenderer implements Renderer {
drawCircle = (r: number) => `vector circle r=${r}`;
drawRectangle = (w: number, h: number) => `vector rect ${w}x${h}`;
}
class RasterRenderer implements Renderer {
drawCircle = (r: number) => `raster pixels for circle r=${r}`;
drawRectangle = (w: number, h: number) => `raster pixels for rect ${w}x${h}`;
}
// Abstraction: holds a renderer and delegates to it.
abstract class Shape {
constructor(protected readonly renderer: Renderer) {}
abstract draw(): string;
}
class Circle extends Shape {
constructor(renderer: Renderer, private readonly radius: number) {
super(renderer);
}
draw = () => this.renderer.drawCircle(this.radius);
}
console.log(new Circle(new VectorRenderer(), 5).draw()); // vector circle r=5
console.log(new Circle(new RasterRenderer(), 5).draw()); // raster pixels for circle r=5
  • Pro: abstraction and implementation grow on independent axes, avoiding a combinatorial class explosion.
  • Pro: you can swap implementations at runtime, since the abstraction only holds a reference.
  • Pro: each hierarchy can be developed, tested, and extended without touching the other.
  • Con: it adds indirection and more moving parts up front, overkill when you only ever have one implementation.
  • Con: designing the two interfaces well requires foresight about which dimensions truly vary.
  • Adapter makes two existing interfaces work together after the fact; Bridge is planned from the start to let two hierarchies evolve separately.
  • Abstract Factory can build and pair the right implementor with an abstraction, often used alongside Bridge.
What does the Bridge pattern separate?
What problem does Bridge prevent compared to plain inheritance?
In the example, what role does the Renderer play?
How does Bridge differ from Adapter in intent?