Bridge Pattern
Concept
Bridge separates an abstraction from its implementation so the two can vary independently, instead of being fused together in a single class hierarchy. The problem it solves shows up whenever a class has two genuinely independent dimensions of variation: subclassing one dimension at a time works fine for a single axis, but subclassing both axes in one inheritance tree produces one subclass per combination — EmailBookingConfirmation, SMSBookingConfirmation, PushBookingConfirmation, EmailReviewRequest, SMSReviewRequest, PushReviewRequest, and so on, growing multiplicatively as either dimension adds a new option. Bridge breaks the two axes into two separate hierarchies — an abstraction hierarchy (what the high-level operation is, e.g. BookingConfirmation, ReviewRequest) and an implementor hierarchy (how the low-level mechanism works, e.g. EmailSender, SMSSender, PushSender) — and has each abstraction hold a reference to an implementor object it delegates the actual work to, rather than inheriting from a mechanism-specific subclass.
The result is that adding a new notification type means one new abstraction subclass, reusable against every existing implementor; adding a new delivery channel means one new implementor subclass, reusable against every existing abstraction — additive growth (M + N classes) instead of multiplicative (M × N). The two hierarchies can also evolve on independent timelines and be maintained by different people/teams without either side needing to know about changes on the other, since the only coupling between them is the implementor's interface.
Tradeoffs
| Approach | Benefit | Cost |
|---|---|---|
| Bridge (two hierarchies, composed via delegation) | Additive growth (M+N classes) as either dimension adds options; each axis evolves independently | Requires identifying the split up front — retrofitting Bridge onto an already-tangled M×N hierarchy is a real refactor, not a drop-in |
| Single inheritance hierarchy covering both dimensions | Simple to start, no upfront design cost | Multiplicative class growth (M×N) as either dimension adds options; a hierarchy that already has "EmailBookingConfirmation" and "SMSReviewRequest" as siblings signals this problem already exists |
| Strategy pattern (inject one varying behavior into an otherwise-fixed class) | Simpler when only one dimension actually varies | Doesn't help once a second genuinely independent dimension needs to vary too — that's exactly the case Bridge targets |
| Dependency injection of a single interface, no formal "abstraction hierarchy" | Least ceremony, common in practice for simple cases | Works fine until the abstraction side itself needs real subclassing (not just configuration) — at that point it's Bridge in substance, just undocumented as such |
Bridge is easy to conflate with plain dependency injection because both involve one class holding a reference to an interface it delegates to; the distinguishing feature of Bridge specifically is that both sides are independently-subclassable hierarchies, not just the implementor side.
When to use / when not to
- Use when a class genuinely varies along two independent dimensions and subclassing both would multiply — notification type × delivery channel, UI widget × rendering platform (the pattern's original GoF motivation), document type × export format.
- Confirm both dimensions are actually independent before reaching for Bridge — if every abstraction only ever pairs with one specific implementor in practice, the two aren't really varying independently and a simpler direct reference or Strategy suffices.
- Don't introduce it preemptively for a single dimension of variation "in case a second one shows up later" — that's speculative abstraction with real cost (two hierarchies to maintain) paid before there's a second axis to justify it.
- Especially worth introducing once an existing hierarchy is already showing the M×N symptom (siblings that clearly combine two concerns in their name) — that's the concrete signal the split is overdue, not merely theoretically applicable.
Common pitfall
Reaching for Bridge based on the shape of the code (an abstraction referencing an interface) without confirming both sides genuinely vary independently in practice — the pattern's whole payoff is additive rather than multiplicative growth, and that payoff only materializes if new abstractions and new implementors actually get added on separate, uncorrelated timelines. If in practice every new "notification type" only ever ships with one predetermined channel, the two axes aren't independent and the two-hierarchy structure is pure overhead: two classes to maintain and understand for every real requirement, where one would have done the job.
Engineering Lens
The clearest tell that Bridge is warranted, in a design review, isn't the abstract "two dimensions of variation" description — it's an existing class hierarchy whose subclass names are visibly compound (EmailBookingConfirmation, SMSReviewRequest) and where a new value on either axis has historically meant adding one subclass per existing value on the other. That's the concrete, retrospective signal that the two concerns should split into independent hierarchies joined by delegation. The pattern's cost is real and shouldn't be waved away: two hierarchies plus the wiring between them is more moving parts than one hierarchy, and for teams where one of the two axes is unlikely to ever grow past 1-2 values, the simplicity of direct inheritance or a single injected dependency is the better tradeoff even with some duplication.
Related
- Composite Pattern — both are structural patterns built on delegation to a held reference, but Composite's reference is to same-typed children in a tree, not a separate implementor hierarchy