Listen to this description of the Bridge Pattern:
[Its purpose is to] decouple an abstraction from its implementation so that the two can vary independently.
How great is that? I have no idea what it means, but it sounds amazing.
I’m kidding (a little). I’ve got some idea what it means. It’s still a lifegoal to some day be able to write that kind of stuff on my own. I mean it’s almost poetic in its use of computer sciency terms, right?
What it means is… how do we write a database adapter implementation for multiple databases? And how do we build ORM — an abstract way of thinking about storing data — on top of the various database adapters? Both the ORM and database adapters are going to change as the code evolves and we shouldn’t have to change everything when we make a small change in one place.
Enter the Bridge Pattern.
Instead of database adapters, let’s talk home automation.
In this example, we’re just starting to think about design. The Bridge Pattern is something that’ll come up when building new code rather than working with existing code.
Borrowing from the fledgling domain terminology, let’s work with voice automations called intents. That’s the abstract idea in this case — a spoken intent that our home automation tool needs to understand and use to cause some action in a device.
There will be “refined” abstractions — things like lightbulb intents and music intents. But, at its core, a
VoiceIntent has a name and several actions a person might take on things being automated.
There are a whole range of actions that we’ll end up with, but we start with supporting four:
help(). Not all of them will be required for every device.
There will be a bunch of devices — a bunch of implementations — so our first instinct was inheritance. Both
MusicIntent inherit from
VoiceIntent and define the methods that make sense for them.
But we’re already in trouble. We have a bunch of placeholder code and it’s clear that we need a whole other set of classes to describe the devices. Those classes will have the responsibility of actually implementing the requested change — physically turning on a light or making actual sound start. These are the “Implementors” in the pattern.
If ORMs are the abstract idea of storing data, things like a MySQL database adapter is the implementor of the actual storage. In our example, a spoken voice intent is the abstract idea. The device is the implementor that makes that abstract idea a reality.
So let’s build some. Start with the interface for a compatible device. It should be able to turn on, off, and maybe loop. By default, they’ll all be false. Concrete implementors — concrete devices — will know how to do these.
For our first concrete implementor, let’s declare a
BasicBulb. It can turn
on(). It can turn
off(). For a real device, there would have to be actual hardware interaction. For now, we assume that both commands work and return
Music device. It can turn on, off, and loop. But it requires a little extra — the name of the playlist or song being played. So… we declare a
selection property to store that… and we override the built-in
toString() method so that the
Music device can report the current selection.
Now that we have implementors and concrete implementors for the abstract idea of voice intents, we need to link the two. We start in the abstract
VoiceIntent baseclass. All
VoiceIntents will have a
CompatibleDevice property. The constructor will now require and assign that property — along with the existing intent
Then, we can redefine the action methods in terms of this device. When an intent starts, we want the device to turn on. If that succeeds — if the device’s
on() method returns
true, then we note success with our
_ok() method. Otherwise, we note failure with the error method.
stop() method for
VoiceIntent looks the same as the
start() method — save that we call
device.off() and the intent is “stop” instead of “start.”
loop() looks the same as well — save that we call
device.loop() and the intent is “loop” instead of “start.”
And that’s our bridge pattern. We have a “bridge” between the abstract idea of voice intents and the concrete device implementations.
We remove the placeholder methods from the refined intents… Then we update the application code to associate devices with voice intents… And… our code is working.
Voice intents are talking to real devices.
Like most patterns, the Bridge Pattern structure isn’t hard. One kind of object has another kind of object as a property. Like allpatterns it’s the why we use it (and the consequences) that are important.
When we’re writing code that has the Bridge Pattern in it, we have an abstract idea — like a voice intent — that needs something to actually make that idea happen — like a device.
And the real fun starts when we look at the consequences of using it… and the different approaches we can take.