Skip to content

Bridge Pattern

The problem

You have two dimensions of variation: the kind of remote control (basic vs. advanced) and the kind of device (TV vs. Radio). If you model this through inheritance, you end up with a class for every combination: BasicRemoteTV, BasicRemoteRadio, AdvancedRemoteTV, AdvancedRemoteRadio. Add a third device type and the count doubles again.

The Bridge pattern breaks this exponential growth. It separates the abstraction (remote control) from the implementation (device) and connects them through composition. You end up with two independent hierarchies. Adding a new device type does not require touching any remote class, and adding a new remote type does not require touching any device class.

Structure

classDiagram
class RemoteControl {
<<abstract>>
#device: Device
+togglePower()
+volumeDown()
+volumeUp()
+channelDown()
+channelUp()
}
class BasicRemote {
+togglePower()
+volumeDown()
+volumeUp()
+channelDown()
+channelUp()
}
class AdvancedRemote {
+mute()
+togglePower()
+volumeDown()
+volumeUp()
+channelDown()
+channelUp()
}
class Device {
<<interface>>
+isEnabled() bool
+enable()
+disable()
+getVolume() int
+setVolume(percent)
+getChannel() int
+setChannel(channel)
}
class TV {
+isEnabled() bool
+enable()
+disable()
+getVolume() int
+setVolume(percent)
+getChannel() int
+setChannel(channel)
}
class Radio {
+isEnabled() bool
+enable()
+disable()
+getVolume() int
+setVolume(percent)
+getChannel() int
+setChannel(channel)
}
RemoteControl <|-- BasicRemote
RemoteControl <|-- AdvancedRemote
Device <|.. TV
Device <|.. Radio
RemoteControl o--> Device : bridge

The bridge is the composition arrow: RemoteControl holds a reference to Device, not a concrete class.

When to use

  • You have two or more independent dimensions of variation and want to extend them separately.
  • You want to switch implementations at runtime (hand a different Device to the same remote instance).
  • You want to hide implementation details from the abstraction’s clients entirely.
  • Inheritance would produce a cartesian-product explosion of subclasses.

Implementation

All three implementations split into two hierarchies: RemoteControl (abstraction) holds a reference to Device (implementation) injected at construction. BasicRemote adds nothing; AdvancedRemote adds mute. Python expresses the split with ABCs on both sides. Go uses struct embedding for RemoteControl inside each concrete remote, giving promoted methods without re-implementing them.

idle
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).

Tradeoffs

ProCon
Adding a new device type never touches any remote classIntroduces more types and indirection, which increases initial complexity
Adding a new remote type never touches any device classThe composition relationship can be harder to trace in an IDE than a straight inheritance chain
Devices can be swapped at runtime without changing the remoteRequires upfront discipline: you must identify the two independent dimensions before designing the hierarchy
Reduces the total number of classes compared to the inheritance approach (N + M instead of N x M)Poorly chosen split points produce a Bridge that is just as rigid as the inheritance it replaced
Each side of the bridge can be tested in isolation using a mock or stub for the other sideOver-engineering risk: if only one device or one remote ever exists, the pattern adds cost with no benefit

Gotchas

  • Choosing the split: the pattern only pays off when both dimensions genuinely vary independently. If the “implementation” side never changes, you have added indirection for nothing.
  • Initialization order: the abstraction must receive its device at construction time (or via a setter). Forgetting to inject a real device and defaulting to null/None/nil causes runtime panics instead of compile errors.
  • Not the same as Strategy: Bridge splits a hierarchy into two independent ones; Strategy replaces a single algorithm inside one object. The structural result looks similar but the intent differs.
  • Reference sharing: if you share one Device instance between two remotes and both call setVolume, they will see each other’s side effects. Decide explicitly whether devices are shared or owned.
  • Go embedding vs. interface: in Go, embedding RemoteControl gives the outer struct promoted methods, but the device field is still accessed through the embedded struct. Verify that your concrete remote calls ar.device, not a shadowed copy.

References

  • Design Patterns: the full GoF catalog this page belongs to
  • Adapter: also reconciles incompatible interfaces, but after the fact rather than by design
  • Strategy: also uses composition over inheritance for variation, but replaces an algorithm rather than splitting a hierarchy
  • Decorator: wraps an object to extend behavior rather than bridging two independent hierarchies