Bridge Pattern

Bridge Pattern

Decouple an abstraction from its implementation so that the two can vary independently.

Intent

The Bridge pattern uses composition instead of inheritance to separate two class hierarchies: the abstraction hierarchy (e.g., Shape) and the implementation hierarchy (e.g., Renderer). Without Bridge, each combination of shape and renderer requires its own subclass — 3 shapes × 4 renderers = 12 concrete classes. With Bridge, the abstraction holds a reference to the implementor interface, reducing that to 3 + 4 = 7 classes.

This is a prospective design decision — made upfront when designing a system that needs both hierarchies to vary independently. This distinguishes Bridge from Adapter, which is a retrospective fix applied to two pre-existing incompatible interfaces.

When NOT to Use

  • Only one implementation exists and no variation is expected — a direct implementation is simpler
  • The abstraction and implementation are stable and unlikely to vary independently — Bridge adds indirection for no benefit
  • You are reconciling existing incompatible interfaces — use Adapter instead

When to Use

  • Both abstraction and implementation need to be extensible via subclassing without a class explosion (e.g., 3 shapes × 4 renderers = 12 classes without Bridge, 7 with Bridge)
  • Implementation details should be hidden from the client and replaceable at runtime

How It Works

Participants: Abstraction (holds a reference to an Implementor; defines the high-level interface), RefinedAbstraction (extends Abstraction with additional behaviour), Implementor (interface defining low-level operations), ConcreteImplementor (implements Implementor).

The Abstraction delegates implementation work to its Implementor rather than implementing it directly. The client interacts with the Abstraction interface; the Implementor used underneath can be swapped at construction time or at runtime.

Class Diagram

classDiagram
    class Abstraction {
        #impl : Implementor
        +operation()
    }
    class RefinedAbstraction {
        +operation()
    }
    class Implementor {
        <<interface>>
        +operationImpl()*
    }
    class ConcreteImplementorA {
        +operationImpl()
    }
    class ConcreteImplementorB {
        +operationImpl()
    }
    Abstraction <|-- RefinedAbstraction
    Abstraction o-- Implementor : bridge (composition)
    Implementor <|.. ConcreteImplementorA
    Implementor <|.. ConcreteImplementorB

TypeScript Example

// Bridge — TypeScript
// Source: GoF Design Patterns, p.151
interface Renderer {
  renderShape(shape: string): void;
}
 
class SVGRenderer implements Renderer {
  renderShape(shape: string) { console.log(`SVG: ${shape}`); }
}
 
abstract class Shape {
  constructor(protected renderer: Renderer) {}
  abstract draw(): void;
}
 
class Circle extends Shape {
  draw() { this.renderer.renderShape("circle"); }
}

Java Example

// Bridge — Java
// Source: GoF Design Patterns, p.151
interface Renderer { void renderShape(String shape); }
 
class SVGRenderer implements Renderer {
    public void renderShape(String shape) { System.out.println("SVG: " + shape); }
}
 
abstract class Shape {
    protected Renderer renderer;
    Shape(Renderer r) { this.renderer = r; }
    abstract void draw();
}
 
class Circle extends Shape {
    Circle(Renderer r) { super(r); }
    @Override public void draw() { renderer.renderShape("circle"); }
}
PatternRelationship
Adapter-PatternAdapter reconciles two existing incompatible interfaces (retrospective); Bridge separates a freshly-designed abstraction from its implementation (prospective)
Strategy-PatternStrategy swaps a single algorithm at runtime; Bridge separates entire class hierarchies. Strategy is a behavioral pattern; Bridge is structural (created Phase 9)
Composite-PatternBoth use recursive composition; Composite focuses on part-whole tree structures

Sources

  • Gamma, Helm, Johnson, Vlissides — Design Patterns, Addison-Wesley, 1994, p.151