The Bridge Pattern is a structural design pattern allowing us to create structures from classes and objects. It divides related classes into two separate hierarchies: abstractions and implementations. The benefit of doing so is that there is no impact of change of functionality in one hierarchy on the other.

Structure

The Bridge pattern consists of the following key components:

  • Abstraction: provides high-level logic for the object
  • Refined Abstraction: extends the Abstraction and provide concrete implementations
  • Implementor: provides an interface which all Concrete Implementors implement, an Abstraction can only communicate with an implementation object provided by this interface
  • Concrete Implementors: concrete implementations of the Implementor
  • Client: works with Abstraction, works with Implementor, and provides the kind of Abstraction required

When to use the Bridge Pattern?

  • When you want to divide a large class which do not meet the Single Responsibility Principle into smaller classes
  • When you want to hide implementation details from the Client

Implementation Example

The code below demonstrates the implementation of the Bridge design pattern in Java. It allows the abstraction (Shape) and the implementation (Color) to change independently of each other. The client class can create different combinations of Shape and Colors by providing the appropriate implementations to the abstractions.

Ideally, without the Bridge pattern, if we have multiple abstraction classes (e.g., Rectangle, Square, Circle, etc.) and multiple implementations (e.g., Green, Red, White, etc.), we would need to create separate subclasses for each combination of shape and color, such as GreenRectangle, RedSquare, WhiteCircle, etc. This would tightly couple the shape and color, making the code inflexible and difficult to extend. The Bridge pattern solves this problem by separating the abstraction (Shape) and implementation (Color) into separate class hierarchies.

This code demonstrates how the Bridge pattern achieves the separation of abstraction and implementation, promoting flexibility and extensibility in the design.

// client class
public class Solution {

    public static void main(String[] args) {
        
        Shape rectangle = new Rectangle(new Green());
        rectangle.draw();

        System.out.println("---------------------");

        Shape circle = new Circle(new Red());
        circle.draw();
    }
}

// abstraction
abstract class Shape {

    protected Color color;

    public Shape(Color color) {

        this.color = color;
    }

    public abstract void draw();
}

// refined abstractions
class Rectangle extends Shape {

    public Rectangle(Color color) {
        
        super(color);
    }

    public void draw() {

        System.out.println("Drawing a Rectangle!");
        color.fillColor();
    }
}

// refined abstraction
class Circle extends Shape {

    public Circle(Color color) {
        
        super(color);
    }

    public void draw() {

        System.out.println("Drawing a Circle!");
        color.fillColor();
    }
}

// implementator
interface Color {

    public void fillColor();
}

// concrete implementor
class Green implements Color {

    public void fillColor() {

        System.out.println("Filling Green Color!");
    }
}

// concrete implementor
class Red implements Color {

    public void fillColor() {

        System.out.println("Filling Red Color!");
    }
}

The output of the following program will be:

Drawing a Rectangle!
Filling Green Color!
---------------------
Drawing a Circle!
Filling Red Color!