Your Guide to Design Patterns – Bridge Pattern

Design Patterns - Bridge Pattern

This week we’ll continue our exploration of design patterns and look at a structural pattern, the Bridge pattern. This pattern is fairly simple, but is not used very often.

The Gang of Four book defines the Bridge pattern as follows:

“Decouple an abstraction from its implementation so that the two can vary independently.”

The Bridge Pattern

The Bridge pattern separates the abstraction from the implementation into two independent but connected hierarchies so that each can vary independently of the other. This allows us to change the classes in each hierarchy independently of the other classes. This minimizes the risk of breaking any existing code, and makes code maintenance easier.

The GoF terminology is a little confusing, but they aren’t talking about interfaces or abstract classes from a programming language perspective here. Their Abstraction is a high-level control layer that isn’t supposed to do any real work on its own. The Abstraction should delegate the work to the Implementation layer . The Abstraction is also called an interface, while the Implementation layer is also called the platform.

A simple example of the Bridge pattern is a household switch which controls lights, air-conditioners, ceiling fans, etc. The purpose of the switch is to turn a device on or off. 

The switch is wired to a specific device, but switches don’t know anything about the devices themselves. We don’t buy a specific LightSwitchFanSwitchAirConSwitch. We buy the same type of switch no matter what device we’re controlling. 

The actual switch can be implemented as a simple two-position lever switch, a wall-mounted switch, a dimmer switch, a pull-chain switch, etc. The orthogonal dimensions are the Device and the Switch. The Device is the Abstraction while the Switch class is the Implementation. The DimmerSwitchLeverSwitch and PullSwitch are the Concrete Implementations.

The driving force behind the Bridge pattern is the often-repeated design principle of *Prefer containment over inheritance*.

Bridge Pattern: Design Example

Back in our first person shooter game, we’ve already created mechanised fighting units, like self-driving armoured vehicles and autonomous fighting robots. We modelled these robots and vehicles as MechanisedFightingVehicles and MechanisedFightingRobots. We can command them to fight and direct them towards specific targets.

We had previously created a FightingUnit interface with a fight() method. Let’s add a few more methods for movement:

public interface FightingUnit {
    public void fight();
    public void turn(int degrees);
    public void advance();
    public void retreat();
}

Each FightingUnit would implement these methods differently. For example, a MechanisedFightingVehicle would turn by turning its front wheels, or if it was running on tank tracks, by braking one track. A MechanisedFightingRobot would turn by taking longer strides with one leg, or if it was running on tracks, by braking one track.

How will we actually control our FightingUnits? We’d probably like to use a remote control in the same way we control drones and model cars in real life.

public interface RemoteControl {
    public void engage();
    public void turn(int degrees);
    public void forward();
    public void back();
}

Initially we’d be happy with a simple RemoteControl with buttons and levers to control movement, speed and direction. But in future we could want more advanced remote controls that can sense our movements, or even be operated by voice commands.

We could model the various remote controls as a single class hierarchy implementing the RemoteControl interface. We will also have a number of different FightingUnit subclasses: MechanisedFightingRobotMechanisedFightingVehicleMechanisedFightingShip, etc.

If we use inheritance to extend the RemoteControl class, we would have to think of all the possible combinations of the various RemoteControl types and the FightingUnit types, and code them as separate classes.

This will lead to an ever-increasing set of different combinations, e.g: BasicRobotRemoteControlAdvancedRobotRemoteControlVoiceActivatedRobotRemoteControlBasicVehicleRemoteControlAdvancedVehicleRemoteControlVoiceActivatedVehicleRemoteControl, etc. If we added other RemoteControl and FightingUnit types later, our class hierarchy would expand accordingly. This is a clumsy design and a nightmare for later modification and maintenance.

Bridge Pattern: Solution Code

Let’s rather use composition instead of inheritance to configure a specific FightingUnit within a RemoteControl.

The Bridge pattern separates the abstraction from the implementation by creating two separate class hierarchies. Each hierarchy can change independently of the other. In our example, the abstraction is the RemoteControl and the implementation is the FightingUnit.

When we use a RemoteControl, it delegates the actual instructions to the contained FightingUnit object. This adds flexibility to our design. Both the RemoteControl and FightingUnit can change independently of the other. It means that:

  • New remote controls can be designed and used in the game without worrying about what they control.
  • New fighting units (robots, vehicles, ships, planes, etc.) can be designed and used in a remote control without changing the remote control code.
  • It simplifies the design, as well as makes later code maintenance easier.

We will create an AbstractRemoteControl class that contains an FightingUnit object which will be supplied at object creation time.

public abstract class AbstractRemoteControl implements RemoteControl {

    // composition/containment 
    private FightingUnit unit;

    // can be used for dependency injection 
    public AbstractRemoteControl(FightingUnit unit) {
        this.unit = unit;
    }

    // common implementations for all remote controls 
    @Override
    public void engage() {
        unit.fight();
    }
    @Override
    public void turn(int degrees) {
        unit.turn(degrees);
    }
    @Override
    public void forward() {
        unit.advance();
    }
    @Override
    public void back() {
        unit.retreat();
    }
    @Override
    public String toString() {
        return getClass().getName() + " is controlling a " + unit;
    }
}

It is obvious from the preceding code that the RemoteControl and the FightingUnit interfaces have different methods. This is more common than both interfaces having the same methods. The RemoteControl (the Abstraction) declares more complex operations that rely on the simpler operations defined by the FightingUnit (the Implementation).

We can create concrete implementations of the AbstractRemoteControl as follows:

public class BasicRemoteControl extends AbstractRemoteControl {

    public BasicRemoteControl(FightingUnit unit) {
        super(unit);
    }
    // override the common implementations as necessary
}

public class VoiceActivatedRemoteControl extends AbstractRemoteControl {

    public VoiceActivatedRemoteControl(FightingUnit unit) {
        super(unit);
    }
    // override the common implementations as necessary 
}

The client code creates the various RemoteControls and passes the desired FightUnits to them. A code snippet follows:

// in client code 
FightingUnit robot   = new MechanisedFightingRobot();
FightingUnit vehicle = new MechanisedFightingVehicle();

AbstractRemoteControl remote1 = new BasicRemoteControl(robot);
AbstractRemoteControl remote2 = new VoiceActivatedRemoteControl(vehicle);

System.out.println(remote1);
System.out.println(remote2);

remote1.forward(); 
remote1.turn(90);
remote1.engage();
remote1.back(); 

remote2.forward(); 
remote2.turn(90);
remote2.engage();
remote2.back(); 

// more code... 

Comparison to Other Patterns

The Bridge pattern has a similar class structure to the Adapter, State and Strategy patterns. All of these patterns are based on composition, which delegates the actual work to other objects. Each pattern has a different intent, however:

  • The Bridge pattern decouples the abstraction and implementation by creating two separate class hierarchies, so they can be changed independently.
  • The Adapter pattern changes the interface of an existing object, and lets two incompatible interfaces work together.
  • As stated by GOF, “Adapter makes things work after they’re designed; Bridge makes them work before”.
  • The State pattern enables an object to change its behaviour when its internal state changes. The state-related work is delegated to a state object.
  • The Strategy pattern allows us to define a family of algorithms and make them interchangeable.

What’s Next?

In the weeks ahead, we’ll continue examining some of the more useful design patterns. Stay tuned!

I’m always interested in your opinion, so please leave a comment. Your feedback helps me write tips that help you.

Leave a Comment

Your email address will not be published. Required fields are marked *

Code like a Java Guru!

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.

Your Java tip is on its way!

Check that incusdata.com is an approved sender, so that your Java tips don’t land up in the spam folder.

Our privacy policy means your data is safe. You can unsubscribe from these tips at any time.