GoF Design Patterns – Bridge Design Pattern

Overview

The Bridge Design Pattern is a structural design pattern that solves the issue of exponential growth of classes that may occur when decomposing the problem being solved into objects.

Imagine that you need to model the following shapes:

  1. Circle
  2. Square
  3. Triangle

And each shape has the following colors available:

  1. Red
  2. Green

If we would try to create a class for each shape, we would end up with 3 x 2 = 6 classes:

  1. RedCircle
  2. RedSquare
  3. RedTriangle
  4. GreenCircle
  5. GreenSquare
  6. GreenTriangle

It is easy to observe that such an approach will not scale and will result in many code duplicates.

We can also observe that this problem has two separate dimensions:

  1. Shape
  2. Color

The Bridge design pattern solves the exponential growth of classes by modeling each dimension separately, allowing for those two dimensions to cooperate using composition.

Two dimensions introduced by the Bridge design pattern are named:

  1. Control layer – also called Abstraction or Interface
  2. Platform Layer – also called Implementation

Note that over here, we do not use Abstraction, Interface, or Implementation in an OOP sense.

The other example of a multi-dimension problem would be the implementation of UI for multiple platforms.

The first dimension would be UI elements:

  1. Button.
  2. Checkbox.

The second dimension would be a platform on which each can run:

  1. Linux.
  2. Windows.
  3. Mac.

If we would try to decompose this problem in a classic way, we would get:

  1. LinuxButton.
  2. LinuxCheckbox.
  3. WindowsButton.
  4. WindowsCheckbox.
  5. MacButton.
  6. MacCheckbox.

We can decompose this problem using separated dimensions using the Bridge design pattern.

Use Cases

  1. Multi-Platform UI Components Framework: In a GUI application, you may have different types of controls (such as buttons, checkboxes, and text boxes) that need to work with different operating systems (such as Linux, Windows, Mac). The Bridge pattern can be used to separate the control’s functionality from its implementation, allowing you to easily add support for new operating systems.
  2. Universal OS Components: You may have different OS components, like Process, Thread, Socket, that need to work on different platforms, like Linux, Windows, Mac. The Bridge pattern can be used to separate the logical functionality from its implementation for a specific platform.
  3. Logging: You may want to log messages to different destinations (such as a file, a database, or a remote server) using different formats (such as plain text, JSON, or XML). The Bridge pattern can be used to separate the logging logic from its implementation, allowing you to easily add support for new destinations and formats.
  4. Platform Independent Mobile Application: mobile application that has a consistent user interface and functionality across both Android and iOS platforms. The Bridge design pattern is used to separate the abstraction (the user interface) from the implementation (the platform-specific code).

Structure

The Bridge pattern has two main components: the Abstraction and the Implementation. The Abstraction (control layer) defines the high-level functionality and maintains a reference to an object of type Implementation (platform layer). The Abstraction provides a control logic, we may say that is orchestrates the Implementation. The Implementation is an abstract class that defines the low-level functionality, and its concrete subclasses provide the actual implementation of the abstraction’s methods. The Abstraction uses the Implementation through a reference to the Implementation.

Example Code

In the below example, we will use the bridge design pattern to create the class structure for UI Framework that will provide two components:

  1. Button
  2. Checkbox

that can work on three platforms:

  1. Linux
  2. Windows
  3. Mac

We will use the following parts of the bridge design pattern:

  1. The Abstraction (control layer): will be implemented by UI Components (Button and Checkbox)
  2. Implementation (platform layer): will be implemented by Platform specific code (LinuxPlatform, WindowsPlatform, MacPlatform)

First, we will create the base Component class, that will hold a reference to the specific Platform:

Java
abstract class Component {
    final Platform platform;

    Component(Platform platform) {
        this.platform = platform;
    }
}

Then, we will create two components, Button:

Java
public class Button extends Component {
    public Button(Platform platform) {
        super(platform);
    }

    public void click() {
        System.out.println("Button was clicked.");
        platform.updateUI();
    }
}

and Checkbox:

Java
public class Checkbox extends Component {
    public Checkbox(Platform platform) {
        super(platform);
    }

    public void check() {
        System.out.println("Checkbox was checked.");
        platform.updateUI();
    }

    public void uncheck() {
        System.out.println("Checkbox was unchecked.");
        platform.updateUI();
    }
}

Then, we will create each Platform on which the component can render:

Java
public class LinuxPlatform implements Platform {
    @Override
    public void updateUI() {
        System.out.println("Updating UI on Linux Platform.");
    }
}

public class WindowsPlatform implements Platform {
    @Override
    public void updateUI() {
        System.out.println("Updating UI on Windows Platform.");
    }
}

public class MacPlatform implements Platform {
    @Override
    public void updateUI() {
        System.out.println("Updating UI on Mac Platform.");
    }
}

Now, we can use the above code in the following way:

Java
Platform linuxPlatform = new LinuxPlatform();
Platform windowsPlatform = new WindowsPlatform();
Platform macPlatform = new MacPlatform();

Button buttonOnLinux = new Button(linuxPlatform);
Button buttonOnWindows = new Button(windowsPlatform);

buttonOnLinux.click();
buttonOnWindows.click();

Checkbox checkboxOnLinux = new Checkbox(linuxPlatform);
Checkbox checkboxOnMac = new Checkbox(macPlatform);

checkboxOnLinux.check();
checkboxOnLinux.uncheck();
checkboxOnMac.check();

Which will produce the following results:

Button was clicked.
Updating UI on Linux Platform.
Button was clicked.
Updating UI on Windows Platform.
Checkbox was checked.
Updating UI on Linux Platform.
Checkbox was unchecked.
Updating UI on Linux Platform.
Checkbox was checked.
Updating UI on Mac Platform.

Notice how in the above code, the bridge design pattern is used to decompose the Multi-Platform UI Components Framework problem in a way that allows us to avoid exponential growth of classes issue.

You can find the source code for this example on GitHub.

Summary

The Bridge Design Pattern solves the exponential growth of classes problems. It is achieved by modeling dimensions as the Abstraction (control layer) and the Implementation (platform layer). Abstraction (control layer) holds a reference to an object from the Implementation (platform layer) and uses it through composition. The pattern is a good fit for problems in which you can observe distinct dimensions, which otherwise will result in N x M amount of classes.

References