Bridge
Overview
Decouple an abstraction from its implementation so both can vary independently.
When to use
- You need to support multiple implementations across multiple abstractions.
- You want to avoid a class explosion from combinations.
Java example
interface Renderer {
String render(String title);
}
class HtmlRenderer implements Renderer {
public String render(String title) { return "<h1>" + title + "</h1>"; }
}
abstract class Page {
protected final Renderer renderer;
protected Page(Renderer renderer) { this.renderer = renderer; }
abstract String view();
}
class ArticlePage extends Page {
private final String title;
ArticlePage(Renderer renderer, String title) {
super(renderer);
this.title = title;
}
String view() { return renderer.render(title); }
}
TypeScript example
interface Renderer {
render(title: string): string;
}
class HtmlRenderer implements Renderer {
render(title: string): string {
return `<h1>${title}</h1>`;
}
}
abstract class Page {
constructor(protected renderer: Renderer) {}
abstract view(): string;
}
class ArticlePage extends Page {
constructor(renderer: Renderer, private title: string) {
super(renderer);
}
view(): string {
return this.renderer.render(this.title);
}
}
Pros and cons
Pros:
- Avoids combinatorial subclasses.
- Implementations are swappable.
Cons:
- More indirection.
- Harder to trace behavior.
Common pitfalls
- Using Bridge when simple inheritance would do.
- Creating too many tiny abstractions.