Bridge
You have two kinds of remote and two kinds of device, so you write BasicTvRemote, BasicRadioRemote, AdvancedTvRemote and AdvancedRadioRemote.
Keep two small hierarchies and join them with a reference.
A class varies in two independent directions
The picture from life
A remote control and a TV are different things. Any remote (basic, advanced) can work with any device (TV, radio) because they meet at a small standard: power, volume. You can buy a better remote or a new TV separately. The standard is the bridge.
The problem
What goes wrong when you do not use it.
You have two kinds of remote and two kinds of device, so you write BasicTvRemote, BasicRadioRemote, AdvancedTvRemote and AdvancedRadioRemote. Add a third device and you write two more classes; add a third remote and you write three more. The class count multiplies.
- A class varies in two independent directions
- You want to swap the implementation at run time
- Subclassing would create a grid of combinations
The idea
How the pattern fixes it.
Keep two small hierarchies and join them with a reference. The Remote holds a Device and only calls the Device's small interface. Now remotes and devices grow separately: 3 + 3 classes instead of 9.
- extends (is a)
- implements
- has a / holds
Who is who
- Remote (abstraction)
- The high-level control. Holds a device.
- AdvancedRemote
- Refines the abstraction with extra buttons.
- Device (implementation)
- The small interface: toggle(), name.
- Tv, Radio
- Concrete devices.
Build it in steps
- 1Find the two dimensions that vary.
- 2Define the small interface for the lower dimension.
- 3Let the upper class hold a reference to it.
- 4Extend each side independently.
Watch it run
Step through the calls. Tap an arrow to jump to it.
The client presses a button on an AdvancedRemote.
press_power()The code
Python and Java. Try writing the classes yourself from the diagram first.
class Tv:
name = "TV"
def __init__(self):
self.on = False
def toggle(self):
self.on = not self.on
return self.name + (" on" if self.on else " off")
class Radio(Tv):
name = "Radio"
class Remote: # the abstraction holds a device (the bridge)
def __init__(self, device):
self.device = device
def press_power(self):
return self.device.toggle()
class AdvancedRemote(Remote):
def press_mute(self):
return self.device.name + " muted"
def demo():
return [Remote(Tv()).press_power(), AdvancedRemote(Radio()).press_power(), AdvancedRemote(Radio()).press_mute()]What you gain
- Two dimensions grow separately: sum of classes, not product
- Swap the implementation at run time
What it costs
- More planning up front
- Not worth it if only one dimension varies
You have already met it in
- JDBC: your code calls the same API, the driver differs per database
- Shape and Colour hierarchies in graphics code
- Logger front-ends with swappable back-ends (SLF4J)
Often mixed up with
Make it stick
Model Shape (Circle, Square) and Renderer (Vector, Raster). Show that adding a Triangle needs one class and adding an OpenGL renderer needs one class.