Bridge Pattern
Decouples an abstraction from its implementation so that the two can vary independently. The Bridge pattern achieves this by replacing inheritance with composition—the abstraction delegates work to an implementation object rather than inheriting the implementation details directly.
✦ Problem
When you try to extend a class in two independent dimensions (for example, shape and color, or UI control and platform), class hierarchy grows exponentially. Adding a new shape type and a new color each doubles the number of subclasses. The class hierarchy becomes bloated, hard to understand, and difficult to modify.
✦ Solution
Switch from inheritance to composition. Extract one of the dimensions into a separate class hierarchy. The original classes reference an object of the new hierarchy instead of containing all its state and behaviors. This reference is the bridge between the two hierarchies. Both hierarchies can grow independently without affecting each other.
Participants
| Role | Responsibility |
|---|---|
| Abstraction | Defines the abstraction's interface and maintains a reference to an Implementor |
| RefinedAbstraction | Extends the interface defined by Abstraction with additional features |
| Implementor | Defines the interface for implementation classes (doesn't have to match Abstraction) |
| ConcreteImplementor | Implements the Implementor interface with a specific behavior |
TypeScript Example
interface Renderer {
renderShape(shape: string, details: string): string;
}
class SVGRenderer implements Renderer {
renderShape(shape: string, details: string) {
return `<svg><${shape} ${details}/></svg>`;
}
}
class CanvasRenderer implements Renderer {
renderShape(shape: string, details: string) {
return `ctx.draw${shape}(${details})`;
}
}
abstract class Shape {
constructor(protected renderer: Renderer) {}
abstract draw(): string;
}
class Circle extends Shape {
constructor(renderer: Renderer, private radius: number) {
super(renderer);
}
draw() {
return this.renderer.renderShape("circle", `r="${this.radius}"`);
}
}
class Rectangle extends Shape {
constructor(renderer: Renderer, private w: number, private h: number) {
super(renderer);
}
draw() {
return this.renderer.renderShape("rect", `w="${this.w}" h="${this.h}"`);
}
}
const svgCircle = new Circle(new SVGRenderer(), 25);
const canvasRect = new Rectangle(new CanvasRenderer(), 100, 50);
console.log(svgCircle.draw());
console.log(canvasRect.draw());Real-World Example
Remote controls and devices represent a classic Bridge example. A remote control (abstraction) can work with TVs, radios, or speakers (implementations) independently. In software, JDBC serves as a bridge between Java applications and database drivers. React Native bridges JavaScript UI code with native platform implementations. Any system where the 'what' and 'how' must evolve independently benefits from this pattern.
✓ When to Use
- •You want to avoid a permanent binding between an abstraction and its implementation
- •Both the abstractions and implementations should be extensible by subclassing
- •You need to switch implementations at runtime
- •You see an exponential growth in class hierarchy due to combining two or more dimensions of variation
✗ When Not to Use
- •There is only one implementation and no foreseeable need for a second
- •The abstraction and implementation are tightly coupled by nature
- •Adding the pattern introduces unnecessary complexity for a simple hierarchy
Understanding the Bridge Pattern
The Bridge pattern is one of the most important structural design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.
Decouples an abstraction from its implementation so that the two can vary independently. The Bridge pattern achieves this by replacing inheritance with composition—the abstraction delegates work to an implementation object rather than inheriting the implementation details directly. The pattern involves the following key participants: Abstraction, RefinedAbstraction, Implementor, ConcreteImplementor. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.
In TypeScript and modern JavaScript development, the Bridge pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Bridge implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.
Knowing when to apply the Bridge pattern is as important as knowing how. Common use cases include: You want to avoid a permanent binding between an abstraction and its implementation; Both the abstractions and implementations should be extensible by subclassing; You need to switch implementations at runtime. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.
The Bridge pattern is related to several other design patterns: Adapter, Abstract Factory, Strategy, State. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.
Related Patterns
Converts the interface of a class into another interface clients expect. Adapter lets classes work together that couldn'…
Provides an interface for creating families of related or dependent objects without specifying their concrete classes. A…
Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary …
Allows an object to alter its behavior when its internal state changes. The object appears to change its class. State en…
More Structural Patterns
Explore All Design Patterns
Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.