Bridge
Split one class hierarchy that varies two ways into two hierarchies joined by composition, so each side grows independently.
Big idea
Bridge separates what something does (the abstraction) from how it gets done (the implementation) and joins them with a reference — the bridge. Two dimensions that would multiply into M × N subclasses become M + N classes that combine at runtime.
The problem it solves
Alerts come in kinds — Incident (urgent, full detail) and Digest (summarised, batched) — and go out over channels — SMS, email, chat. Model that with inheritance and you write IncidentSms, IncidentEmail, IncidentChat, DigestSms, … Six classes for 2 × 3. Add a voice-call channel: two more. Add a "reminder" kind: three more. Every combination is a class, and the formatting logic for each kind is duplicated across channels.
Think of it like
A universal TV remote and the TVs it controls. The remote's buttons (power, volume, a "kids mode" macro) are the abstraction; each TV brand's infrared codes are the implementation. You can buy a fancier remote without buying a new TV, and a new TV works with the old remote — because they meet at a small, agreed set of signals.
Structure
- Abstraction (
Alert) — the high-level behaviour; holds an implementation. - Refined abstractions (
Incident,Digest) — variants of the behaviour. - Implementation (
Channel) — the low-level primitive operations. - Concrete implementations (
Sms,Email,Chat).
In code
class Sms:
def deliver(self, title, body):
return f"SMS | {title}: {body}"[:48] # SMS is short
class Email:
def deliver(self, title, body):
return f"EMAIL | Subject: {title} | {body}"
class Chat:
def deliver(self, title, body):
return f"CHAT | *{title}* {body}"
class Alert:
def __init__(self, channel):
self.channel = channel # the bridge
class Incident(Alert):
def send(self, events):
worst = max(events, key=lambda e: e[1])
return self.channel.deliver("INCIDENT", f"{worst[0]} at severity {worst[1]}")
class Digest(Alert):
def send(self, events):
names = ", ".join(e[0] for e in events)
return self.channel.deliver("Daily digest", f"{len(events)} events: {names}")
events = [("disk full", 3), ("cpu spike", 2), ("db down", 5)]
for alert_kind in (Incident, Digest):
for channel in (Sms(), Email(), Chat()):
print(alert_kind(channel).send(events))output
SMS | INCIDENT: db down at severity 5
EMAIL | Subject: INCIDENT | db down at severity 5
CHAT | *INCIDENT* db down at severity 5
SMS | Daily digest: 3 events: disk full, cpu s
EMAIL | Subject: Daily digest | 3 events: disk full, cpu spike, db down
CHAT | *Daily digest* 3 events: disk full, cpu spike, db downRun and edit it in the app.
Adding a voice channel is one new class that works with both kinds. Adding a "reminder" alert is one new class that works with all three channels.
You have 4 kinds of report and 3 output formats. How many classes for the variants with Bridge, versus one subclass per combination?
Watch it
How to implement it
- Spot the two independent dimensions (what vs how; platform vs feature; shape vs renderer).
- Define the implementation interface with the primitive operations the abstraction needs.
- Make the abstraction hold an implementation and express its behaviour in terms of those primitives.
- Choose the pairing at runtime — often via configuration or an Abstract Factory.
When to use it — and when not to
Use it when a class is growing along two axes, when you want to switch implementations at runtime, or when platform-specific code (OS, database, renderer) should stay out of feature code. Skip it when there's only one real dimension — a plain interface is enough.
Pros and cons
| Pros | Cons |
|---|---|
| M + N classes instead of M × N | Up-front design; easy to split along the wrong seam |
| Abstraction and implementation evolve separately | Extra indirection for simple cases |
| Swap implementation at runtime | The implementation interface must suit every abstraction |
How it relates
- It's composition over inheritance applied to two hierarchies at once.
- Adapter is retrofitted between existing classes; Bridge is designed in up front.
- Strategy looks similar (an object holding a swappable part), but Strategy swaps an algorithm; Bridge splits a whole hierarchy.
- Abstract Factory can choose matching implementations.
How does Bridge differ from Strategy, given both hold a swappable object?
Key terms
- Bridge — A pattern that separates an abstraction from its implementation so both can vary independently.
- Abstraction — The high-level interface clients use, holding a reference to an implementation.
- Implementation — The low-level interface the abstraction delegates its primitive operations to.
- Class explosion — The M × N growth of subclasses when two dimensions are combined by inheritance.