Decouple an abstraction from its implementation so that the two can vary independently.
Abstraction? Implementation? Sounds scary, right? Let's see what does this really mean.
The "Abstraction", also known as the "Interface", is like a control part of some entity. It does not do any real work by itself. It delegates work to other objects and controls how the work is performed. Those other objects are called "Implementation" (or "Platforms").
Imagine that you're developing an app for booking airline tickets. You begun with a single airline company, but then after a while, added support for dozen airlines. All of them have different APIs, so your code looks like a salad of
Any change to user interface code somehow touches the code of airline APIs. And adding new airline forces you to make changes in all UI classes. When some airline API does not support certain feature that all other airline support, you have to put several conditional blocks to the UI code.
This mess is very hard to support. With every change your progress slows down to a crawl.
The Bridge pattern suggests to organize the code into two parts:
- Abstraction: UI of the app.
- Implementation: a whole layer of communication with airline APIs.
Abstraction will handle all interactions with user, but delegate the real booking work to one of the Implementation objects, corresponding to API of a selected airline. Thus, implementation objects could be swappable, as long as they follow a common interface.
This way you could make changes to the app's UI without changing code that handles work with APIs, and vise versa.
Abstraction mainly contains a control logic or user interface. Abstraction's code relies on the Implementation object to do the real job.
Implementation defines a common interface that Abstraction classes use to delegate work to implementation objects.
The Abstraction's and Implementation's interfaces may either be the same or entirely different. In later case, Implementation contains some basic primitive operations used by Abstraction to perform some complex behavior.
Concrete Implementations contain platform-specific code.
Extended Abstractions may be created to provide several variants of the control logic. These classes, same as their parent, should work with implementation objects using their common interface.
Client should mostly work with Abstraction objects. Clients usually pass a Concrete Implementation object to the Abstraction object once, during its construction. However, implementation can also be changed dynamically if needed.
// All remote classes contain reference to the device they controls. Remote's // methods delegate most of the work to the device methods. class Remote is field device: Device constructor BasicRemote(device: Device) is this.device = device method togglePower() is if device.isEnabled() then device.disable() else device.enable() method volumeDown() is device.setVolume(device.getVolume() - 10) method volumeUp() is device.setVolume(device.getVolume() + 10) method channelDown() is device.setChannel(device.getChannel() - 1) method channelUp() is device.setChannel(device.getChannel() + 1) // You can extend remote hierarchy independently from device classes. class AdvancedRemote extends BasicRemote is method mute() is device.setVolume(0) // All devices have the common interface. This makes them compatible with // all remotes. interface Device is method isEnabled() method enable() method disable() method getVolume() method setVolume(percent) method getChannel() method setChannel(channel) // But each concrete device may have its own implementation. class Tv implements Device is // ... class Radio implements Device is // ... // Somewhere in client code. tv = new Tv(); remote = new Remote(tv) remote.pover() radio = new Radio(); remote = new AdvancedRemote(radio)
Pros and Cons
- Allows building platform independent code.
- Follows the Open/Closed Principle.
- Hides implementation details from client.
- Increases overall code complexity by creating multiple additional classes.
Relations with Other Patterns
Bridge is designed up-front to let the abstraction and the implementation vary independently. Adapter is retrofitted to make unrelated classes work together. Adapter makes things work after they're designed; Bridge makes them work before they are.
State, Strategy, Bridge (and to some degree Adapter) have similar solution structures. They all share elements of the "handle/body" idiom. They differ in intent - that is, they solve different problems.
Abstract Factory can be used along with a Bridge pattern. It's useful when the "interface" part of the Bridge can work only with a particular "implementation". In this case, factory can encapsulate these relations and hide the complexity from a client.