27

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?

0

3 Answers 3

18

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*/}
3
  • 5
    Very nice example. I would add my two cents: this is very good example of preferring composition over inheritance
    – zzfima
    Commented Nov 13, 2013 at 5:22
  • 1
    For what it's worth, I think it should be 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
    – ErikE
    Commented Sep 2, 2016 at 22:29
  • This line "the two different abstractions, train transport and acceleration" helps, point out what is the two hierarchies and what we try to solve is to make them vary independently.
    – wangdq
    Commented Jun 12, 2017 at 2:43
15

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".

2
  • I don't understand how this is suppose to be the bridge pattern? When you add a new component to the Widget hierarchy, you are forced to add that new Widget to ALL the Painters (Win32NewWidgetPainter, PPCNewWidgetPainter). This is NOT two independently growing hierarchies. For a proper bridge pattern, you would not subclass PlatformWidgetPainter class for each widget, rather have it receive a Widget "draw descriptor".
    – Mazyod
    Commented Dec 23, 2013 at 3:47
  • Thanks for the feedback, you are right. It was years ago that I posted this, and reviewing it now I would say it describes bridge well until the last bit where I derived 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.
    – tcarvin
    Commented Dec 24, 2013 at 15:00
3

The bridge intends to decouple an abstraction from its concrete implementation, so that both can vary independently:

  • refine the abstraction with subclassing
  • provide different implementations, also by subclassing, without having to know neither the abstraction nor its refinements.
  • if needed, choose at runtime which is the most suitable implementation.

The bridge achieve this by using composition:

  • the abstraction refers (reference or pointer) to an implementation object
  • the abstraction and its refinements only known the implementation interface

enter image description here

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:

  • adapters are often used when an incompatibility is discovered and the coupling is unforeseen
  • bridges are used from the beginning of the design, when it is expected that the classes would evolve independently.