I do not understand the "bridge" design pattern at all. I have gone through various web sites, but they haven't helped.
Can anybody help me in understanding this?
In OOP we use polymorphism so an abstraction can have multiple implementations. Let's look at the following example:
//trains abstraction
public interface Train
{
move();
}
public class MonoRail:Train
{
public override move()
{
//use one track;
}
}
public class Rail:Train
{
public override move()
{
//use two tracks;
}
}
A new requirement introduced and needs to bring in the acceleration perspective of the trains, so change the code as below.
public interface Train
{
void move();
}
public class MonoRail:Train
{
public override void move()
{
//use one track;
}
}
public class ElectricMonoRail:MonoRail
{
public override void move()
{
//use electric engine on one track.
}
}
public class DieselMonoRail: MonoRail
{
public override void move()
{
//use diesel engine on one track.
}
}
public class Rail:Train
{
public override void move()
{
//use two tracks;
}
}
public class ElectricRail:Rail
{
public override void move()
{
//use electric engine on two tracks.
}
}
public class DieselRail: Rail
{
public override void move()
{
//use diesel engine on two tracks.
}
}
The above code is not maintainable and lacks reusability (assuming we could reuse the acceleration mechanism for the same track platform). The following code applies the bridge pattern and separates the two different abstractions, train transport and acceleration.
public interface Train
{
void move(Accelerable engine);
}
public interface Accelerable
{
public void accelerate();
}
public class MonoRail:Train
{
public override void move(Accelerable engine)
{
//use one track;
engine.accelerate(); //engine is pluggable (runtime dynamic)
}
}
public class Rail:Train
{
public override void move(Accelerable engine)
{
//use two tracks;
engine.accelerate(); //engine is pluggable (runtime dynamic)
}
}
public class ElectricEngine:Accelerable{/*implementation code for accelerable*/}
public class DieselEngine:Accelerable{/*implementation code for accelerable*/}
Monorail
since it's not really two words, it's a single (compound) word. A MonoRail would be some subclass of Rail instead of a different kind of rail (which it is). Just like we wouldn't use SunShine
or CupCake
, they would be Sunshine
and Cupcake
While most design patterns have helpful names, I find the name "Bridge" to be non-intuitive in regards to what it does.
Conceptually, you push the implementation details used by a class hierarchy into another object, typically with its own hierarchy. By doing so, you are removing a tight dependency on those implementation details and allowing the details of that implementation to change.
On a small scale, I liken this to the use of a strategy pattern in the way you can plug in a new behavior. But instead of just wrapping an algorithm as is often seen in a strategy, the implementation object is usually more feature filled. And when you apply the concept to an entire class hierarchy the larger pattern becomes a Bridge. (Again, hate the name).
It is not a pattern that you will use every day, but I have found it helpful when managing a potential explosion of classes that can happen when you have an (apparent) need for multiple inheritance.
Here is a real-world example:
I have a RAD tool that lets you drop and configure controls on a design surface, so I have an object model like this:
Widget // base class with design surface plumbing
+ Top
+ Left
+ Width
+ Height
+ Name
+ SendToBack
+ BringToFront
+ OnPropertyEdit
+ OnSelect
+ Validate
+ ShowEditor
+ Paint
+ Etc
TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor // override base to show a property editor form specific to a Textbox
+ Paint // override to render a textbox onto the surface
+ Etc
ListWidget : Widget // list specific
+ Items
+ SelectedItem
+ ShowEditor // override base to show a property editor form specific to a List
+ Paint // override to render a list onto the surface
+ Etc
And so on, with perhaps a dozen controls.
But then a new requirement is added to support multiple themes (look-n-feels). Let's say we have the following themes: Win32
, WinCE
, WinPPC
, WinMo50
, WinMo65
. Each theme would have different values or implementations for rendering-related operations like DefaultFont, DefaultBackColor, BorderWidth, DrawFrame, DrawScrollThumb, etc.
I could create an object model like this:
Win32TextboxWidget : TextboxWidget
Win32ListWidget : ListWidget
etc., for one control type
WinCETextboxWidget : TextboxWidget
WinCEListWidget : ListWidget
etc., for each other control type (again)
You get the idea—you get a class explosion of the # of widgets times the # of themes. This complicates the RAD designer by making it aware of each and every theme. Plus, adding new themes forces the RAD designer to be modified. Further, there is a lot of common implementation within a theme that it would be great to inherit, but the controls are already inheriting from a common base (Widget
).
So instead what I did is create a separate object hierarchy that implements the theme. Each widget would hold a reference to the object that implements the rendering operations. In many texts, this class is suffixed with an Impl
but I deviated from that naming convention.
So now my TextboxWidget
looks like this:
TextboxWidget : Widget // text box specific
+ Text
+ MaxLength
+ Font
+ ShowEditor
+ Painter // reference to the implementation of the widget rendering operations
+ Etc
And I can have my various painters inherit my theme-specific base, which I could not do before:
Win32WidgetPainter
+ DefaultFont
+ DefaultFontSize
+ DefaultColors
+ DrawFrame
+ Etc
Win32TextboxPainter : Win32WidgetPainter
Win32ListPainter : Win32WidgetPainter
One of the nice things is that I can dynamically load the implementations at run-time, allowing me to add as many themes as I want without changing the core software. In other words my "implementation can vary independent of the abstraction".
Win32TextboxPainter
and Win32ListPainter
from Win32WidgetPainter
. You can have an inheritance tree on the implementation side, but it should be more generic (perhaps StaticStyleControlPainter
, EditStyleControlPainter
, and ButtonStyleControlPainter
) with any needed primitive operations overridden as needed. This is closer to the real code I was basing the example on.
The bridge intends to decouple an abstraction from its concrete implementation, so that both can vary independently:
The bridge achieve this by using composition:
Additional remarks on a frequent confusion
This pattern is very similar to the adapter pattern: the abstraction offers a different interface for an implementation and uses composition to do so. But:
The key difference between these patterns lies in their intents
- Gamma & al, in "Design patterns, element of reusable OO software", 1995
In this excellent seminal book on design patterns, the authors also observe that: