© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_12

12. Bridge Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

This chapter covers the bridge pattern.

GoF Definition

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

Concept

This pattern is also known as the handle/body pattern , in which you separate an implementation from its abstraction and build separate inheritance structures for them. Finally, you connect them through a bridge.

You must note that the abstraction and the implementation can be represented either through an interface or an abstract class, but the abstraction contains a reference to its implementer. Normally, a child of an abstraction is called a refined abstraction and a child of an implementation is called a concrete implementation .

This bridge interface makes the functionality of concrete classes independent from the interface implementer classes. You can alter different kinds of classes structurally without affecting each other.

Real-World Example

In a software product development company, the development team and the marketing team both play a crucial role. Marketing teams do market surveys and gather customers’ needs, which may vary depending on the nature of the customers. Development teams implement those requirements in their products to fulfill the customers’ needs. Any change (e.g., in the operational strategy) in one team should not have a direct impact on the other team. Also, when new requirements come from the customer side, it should not change the way that developers work in their organization. In a software organization, the marketing team plays the role of the bridge between the clients and the development team.

Computer-World Example

GUI frameworks can use the bridge pattern to separate abstractions from platform-specific implementation. For example, using this pattern, it can separate a window abstraction from a window implementation for Linux or macOS.

Note

In Java, you may notice the use of JDBC, which provides a bridge between your application and a particular database. For example, the java.sql.DriverManager class and the java.sql.Driver interface can form a bridge pattern where the first one plays the role of abstraction and the second one plays the role of implementor. The concrete implementors are com.mysql.jdbc.Driver or oracle.jdbc.driver.OracleDriver, and so forth.

Illustration

Suppose that you are a remote-control maker and you need to make remote controls for different electronic items. For simplicity, let’s assume that you are presently getting orders to make remote controls for televisions and DVD players. Let’s also assume that your remote control has two major functionalities: on and off.

You may want to start with the design shown in Figure 12-1 or the one shown in Figure 12-2.
../images/395506_2_En_12_Chapter/395506_2_En_12_Fig1_HTML.png
Figure 12-1

Approach 1

../images/395506_2_En_12_Chapter/395506_2_En_12_Fig2_HTML.png
Figure 12-2

Approach 2

On further analysis, you discover that Approach 1 is truly messy and difficult to maintain.

At first, Approach 2 looks cleaner, but if you want to include new states, such as sleep, mute, and so forth, or if you want to include new electronic items, such as AC, DVD, and so on, you face new challenges because the elements are tightly coupled in this design approach. But in a real-world scenario, this kind of enhancement is often required.

This is why, you need to start with a loosely coupled system for future enhancements so that either of the two hierarchies (electronics items and their states) can grow independently. The bridge pattern perfectly fits this scenario.

Let’s start with the most common bridge pattern class diagram (see Figure 12-3).
../images/395506_2_En_12_Chapter/395506_2_En_12_Fig3_HTML.jpg
Figure 12-3

A classic bridge pattern

  • Abstraction (an abstract class) defines the abstract interface and it maintains the Implementor reference.

  • RefinedAbstraction (a concrete class) extends the interface defined by Abstraction.

  • Implementor (an interface) defines the interface for implementation classes.

  • ConcreteImplementor (Concrete class) implements the Implementor interface.

I followed a similar architecture in the following implementation. For your ready reference, I have pointed out all the participants in the following implementation with comments.

Class Diagram

Figure 12-4 shows the class diagram.
../images/395506_2_En_12_Chapter/395506_2_En_12_Fig4_HTML.jpg
Figure 12-4

Class diagram

Package Explorer View

Figure 12-5 shows the high-level structure of the program.
../images/395506_2_En_12_Chapter/395506_2_En_12_Fig5_HTML.jpg
Figure 12-5

Package Explorer view

Key Characteristics

Here are the key characteristics of the following implementation.
  • The ElectronicGoods abstract class plays the role of abstraction. The State interface plays the role of the implementor.

  • The concrete implementors are OnState class and OffState class. They have implemented the moveState() and hardPressed()interface methods as per their requirements.

  • The ElectronicGoods abstract class holds a reference of the State implementor.

  • The abstraction methods are delegating the implementation to the implementor object. For example, notice that hardButtonPressed() is actually shorthand for state.hardPressed(), where state is the implementor object.

  • There are two refined abstractions: Television and DVD. The class is happy with the methods that it inherits from its parent. But the DVD class wants to provide an additional feature, so it implements a DVD-specific method: doublePress(). The doublePress() method is coded in terms of superclass abstraction only.

Implementation

Here is the implementation.
package jdp2e.bridge.demo;
//Implementor
interface State
{
    void moveState();
    void hardPressed();
}
//A Concrete Implementor.
class OnState implements State
{
    @Override
    public void moveState()
    {
        System.out.print("On State ");
    }
    @Override
    public void hardPressed()
    {
        System.out.print(" The device is already On.Do not press the button so hard. ");
    }
}
//Another Concrete Implementor.
class OffState implements State
{
    @Override
    public void moveState()
    {
        System.out.print("Off State ");
    }
    @Override
    public void hardPressed()
    {
        System.out.print(" The device is Offline now.Do not press the off button again. ");
    }
}
//Abstraction
abstract class ElectronicGoods
{
    //Composition - implementor
    protected State state;
    /*Alternative approach:
      We can also pass an implementor (as input argument) inside a constructor.
     */
    /*public ElectronicGoods(State state)
    {
        this.state = state;
    }*/
    public State getState()
    {
        return state;
    }
    public void setState(State state)
    {
        this.state = state;
    }
    /*Implementation specific:
      We are delegating the implementation to the Implementor object .
     */
    public void moveToCurrentState()
    {
        System.out.print("The electronic item is functioning at : ");
        state.moveState();
    }
    public void hardButtonPressed()
    {
        state.hardPressed();
    }
}
//Refined Abstraction
//Television does not want to modify any superclass method.
class Television extends ElectronicGoods
{
    /*public Television(State state)
    {
        super(state);
    }*/
}
/*DVD class also ok with the super class method.
In addition to this, it uses one additional method*/
class DVD extends ElectronicGoods
{
    /*public DVD(State state)
    {
        super(state);
    }*/
    /* Notice that following DVD specific method is coded with superclass methods but not with the implementor (State) method.So, this approach will allow to  vary the abstraction and implementation independently .
     */
    public void doublePress() {
        hardButtonPressed();
        hardButtonPressed();
    }
}
public class BridgePatternDemo {
    public static void main(String[] args) {
        System.out.println("***Bridge Pattern Demo***");
        System.out.println(" Dealing with a Television at present.");
        State presentState = new OnState();
        //ElectronicGoods eItem = new Television(presentState);
        ElectronicGoods eItem = new Television();
        eItem.setState(presentState);
        eItem.moveToCurrentState();
        //hard press
        eItem.hardButtonPressed();
        //Verifying Off state of the Television now
        presentState = new OffState();
        //eItem = new Television(presentState);
        eItem.setState(presentState);
        eItem.moveToCurrentState();
        System.out.println(" Dealing with a DVD now.");
        presentState = new OnState();
        //eItem = new DVD(presentState);
        eItem = new DVD();
        eItem.setState(presentState);
        eItem.moveToCurrentState();
        presentState = new OffState();
        //eItem = new DVD(presentState);
        eItem = new DVD();
        eItem.setState(presentState);
        eItem.moveToCurrentState();
        //hard press-A DVD specific method
        //(new DVD(presentState)).doublePress();
        ((DVD)eItem).doublePress();
        /*The following line of code will cause error because a television object does not have this method.*/
        //(new Television(presentState)).doublePress();
    }
}

Output

Here is the output.
***Bridge Pattern Demo***
 Dealing with a Television at present.
The electronic item is functioning at : On State
The device is already On.Do not press the button so hard.
The electronic item is functioning at : Off State
 Dealing with a DVD now.
The electronic item is functioning at : On State
The electronic item is functioning at : Off State
    The device is Offline now.Do not press the off button again.
    The device is Offline now.Do not press the off button again.

Q&A Session

  1. 1.

    This pattern looks similar to a state pattern. Is this correct?

    No. The state pattern falls into the behavioral pattern and its intent is different. In this chapter, you have seen an example where the electronic items can be in different states, but the key intent was to show that
    • How you can avoid tight coupling between the items and their states.

    • How you can maintain two different hierarchies and both of them can extend without making an impact to each other.

     

In addition to these points, you are dealing with multiple objects in which implementations are shared among themselves.

For a better understanding, go through the comments that are attached with this implementation. I am also drawing your attention to the DVD-specific doublePress() method . Notice that it is constructed with superclass methods, which in turn delegate the implementation to the implementor object (a state object in this case). This approach allows you to vary the abstraction and implementation independently, which is the key objective of the bridge pattern.
  1. 2.

    You could use simple subclassing instead of this kind of design. Is this correct?

    No. With simple subclassing, your implementations cannot vary dynamically. It may appear that the implementations behave differently with subclassing techniques, but actually, those kinds of variations are already bound to the abstraction at compile time.

     
  2. 3.

    In this example, I see lots of dead code. Why are you keeping those?

    Some developers prefer constructors over Getter/Setter methods. You can see the variations in different implementations. I am keeping those for your ready reference. You are free to use any of them.

     
  3. 4.
    What are key advantages of using a bridge design pattern?
    • The implementations are not bound to the abstractions.

    • Both the abstractions and the implementations can grow independently.

    • Concrete classes are independent from the interface implementer classes (i.e., changes in one of these does not affect the other). You can also vary the interface and the concrete implementations in different ways.

     
  4. 5.
    What are the challenges associated with this pattern?
    • The overall structure may become complex.

    • Sometimes it is confused with the adapter pattern. (The key purpose of an adapter pattern is to deal with incompatible interfaces only.)

     
  5. 6.

    Suppose I have only one state; for example, either OnState or OffState. In this case, do I need to use the State interface?

    No, it is not mandatory. GoF classified this case as a degenerate case of the bridge pattern.

     
  6. 7.

    In this example, an abstract class is used to represent an abstraction and an interface is used for an implementation. Is it mandatory?

    No. You can also use an interface for abstraction. Basically, you can use either of an abstract class or an interface for any of the abstractions or implementations. I simply used this format for better readability.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.107.229