Bridge Design Pattern

The Bridge Design Pattern is a structural pattern that separates abstraction from implementation, allowing both to evolve independently. It decouples abstract class and implementation classes. Bridge pattern provides an interface which acts as a bridge between two class hierarchies.


Sometimes we confuse Bridge pattern with Adapter. Adapter pattern is used to communicate between two existing incompatible interfaces whereas Bridge is used to decouple client's code from implementation. This pattern is particularly useful when there are multiple dimensions of variation in a system and you want to avoid a permanent binding between an abstraction and its implementation.


Structure of the Bridge Design Pattern

The Bridge Design Pattern involves the following key components:
  • Abstraction : This is the high-level interface that clients interact with. It contains a reference to the implementation and defines high-level operations. Abstraction is independent of the specific implementation.

  • Refined Abstraction : This is an extended version of the abstraction. It can add new features or behaviors that build on the basic abstraction. Refined abstractions are optional and not always required.

  • Implementation : This is the interface that defines the operations that can be performed on the implementation side. It is independent of the abstraction and serves as the bridge between abstraction and concrete implementations.

  • Concrete Implementation : This is the class that provides the actual implementation of the operations defined by the implementation interface. There can be multiple concrete implementations for different variations.

Advantages of Bridge Pattern

  • Decouples Abstraction and Implementation : It allows independent evolution of both components, minimizing the impact of changes in one on the other.The primary advantage of the Bridge pattern is its ability to decouple abstraction from implementation.

  • Flexibility and Extensibility : The Bridge pattern enhances flexibility and extensibility by Provides a framework for independent variation of abstraction and implementation, facilitating the addition of new features without modifying existing code.

  • Reduced Proliferation of Classes : It Helps in managing the number of classes, avoiding a proliferation that could result from direct coupling between abstraction and implementation.

  • Open-Closed Principle : The Bridge pattern adheres to the Open-Closed Principle, enabling the addition of new abstractions or implementations without altering existing code, promoting a modular and scalable system.

  • Adaptable to Change : The Bridge pattern makes the system more adaptable to change. It enhances the system's adaptability to changes, as modifications in one part (abstraction or implementation) do not affect the other.

  • Separation of Concerns : The pattern promotes a clear separation of concerns by isolating the abstraction from the details of its implementation. This separation enhances code readability and maintainability.

  • Supports Multiple Platforms : Bridge pattern accommodates variations for supporting multiple platforms or environments, the Bridge pattern prevents code duplication and provides a mechanism for handling platform-specific differences.

  • Promotes Interface-Based Design : The Bridge pattern encourages the definition of abstractions and implementations through interfaces. This design approach leads to cleaner, modular code by following an interface-based design approach.
By leveraging these advantages, the Bridge Design Pattern becomes a powerful tool for achieving flexibility, maintainability, and extensibility in software systems. It is particularly beneficial in scenarios where there are multiple dimensions of variation, and a clear separation between abstraction and implementation is desirable.

When we should use Bridge pattern

  • When we want to decouple an abstraction from its implementation so that the two can vary independently.
  • When we want run-time binding of the implementation.
  • When we want to vary implementation without changing client's code.

Implementation of Bridge Design Pattern

We will use Bird as the interface for implementation classes and Crow.java and Duck.java as concrete implementation of Bird interface. Flyable is an abstract class which "HAS A" reference of Bird(implementer interface). FlyingCharacters are concrete implementation of Flyable abstract class. FlyingBirds.java is the client which uses FlyingCharacters class to create various flyable objects and move them using Bird Interface.

Bridge Design Pattern UML Diagram Bird.java
public interface Bird {
    public void makeSound();
    public void gotoPosition(int x, int y);
}

Crow.java and Duck.java are Concrete implementation of Bridge implementer interface.

Crow.java
public class Crow implements Bird {
    private String type;
    private int positionX;
    private int positionY;
 
    public Crow(String type){
        this.type = type;
    }
 
    @Override
    public void gotoPosition(int x, int y){
        System.out.println("Crow is moving to position (" + x + "," + y + ")");
        this.positionX = x;
        this.positionY = y;
    }
 
    @Override
    public void makeSound(){
        System.out.println("Caw Caw ....");
    }
}
Duck.java
public class Duck implements Bird {
    private String type;
    private int positionX;
    private int positionY;
 
    public Duck(String type){
        this.type = type;
    }
 
    @Override
    public void gotoPosition(int x, int y){
        System.out.println("Duck is moving to position (" + x + "," + y + ")");
        this.positionX = x;
        this.positionY = y;
    }
 
    @Override
    public void makeSound(){
        System.out.println("Quack Quack ....");
    }
}

Flyable is an abstract class which defines the abstraction interface.

Flyable.java
public abstract class Flyable {
    protected Bird bird;
 
    protected Flyable(Bird bird){
        this.bird = bird;
    } 
 
    public abstract void tweet();
    public abstract void move(int x, int y);
}
FlyingCharacters.java
public class FlyingCharacters extends Flyable {
    private int x, y;
 
    public FlyingCharacters(int x, int y, Bird bird){
        super(bird);
        this.x = x;
        this.y = y;
    }
 
    public void tweet(){
        bird.makeSound();
    }
 
    public void move(int x, int y){
        bird.gotoPosition(x, y);
        this.x = x;
        this.y = y;
    }
}
FlyingBirds.java
public class FlyingBirds {
    public static void main(String[] args) {
     Flyable duck = new FlyingCharacters(0, 0, new Duck("Duck"));
     Flyable crow = new FlyingCharacters(0, 0, new Crow("Crow"));
        System.out.println("Moving Birds");
     for(int i = 1; i < 5; i++){
         // Moving Duck
         duck.move(3*i, 3*i);
         // Moving Crow
         crow.move(4*i,  4*i);
         // Produce sound 
         duck.tweet();
         crow.tweet();
         System.out.println("----------------------");
     }
    }
}

Output

Moving Birds
Duck is moving to position (3,3)
Crow is moving to position (4,4)
Quack Quack ....
Caw Caw ....
----------------------
Duck is moving to position (6,6)
Crow is moving to position (8,8)
Quack Quack ....
Caw Caw ....
----------------------
Duck is moving to position (9,9)
Crow is moving to position (12,12)
Quack Quack ....
Caw Caw ....
----------------------
Duck is moving to position (12,12)
Crow is moving to position (16,16)
Quack Quack ....
Caw Caw ....
----------------------

Best Practices of Bridge Pattern

  • Clearly identify the dimensions of variation in your system that can benefit from decoupling. This may involve abstraction and implementation that evolve independently.

  • Use composition to link the abstraction and implementation instead of relying on inheritance. This provides flexibility and avoids issues related to multiple inheritance.

  • Design abstraction and implementation interfaces carefully. Ensure that they capture the essential behaviors without unnecessary dependencies. Interface-based design facilitates the independence of abstraction and implementation.

  • The client code should interact with the abstraction and be unaware of the specific implementation details. Decoupling client code from implementation details allows for easier changes in the future.

  • Design the abstraction to be flexible and extensible. Avoid locking the abstraction into specific implementations, allowing for easy addition of new implementations in the future.

  • Apply dependency injection to inject the implementation into the abstraction. This approach allows for dynamic changes in the implementation, promoting flexibility and testability.

  • Clearly document the intent of the abstraction and implementation. Make it easy for developers to understand how the bridge pattern is applied and what variations are possible.

  • Use refined abstractions when there is a need to extend the basic abstraction. Refined abstractions can add new features or behaviors while still relying on the bridge pattern for the underlying implementation.

Related Topics
Facade Design Pattern
Mediator Design Pattern
Interpreter Design Pattern
Factory Design Pattern
Abstract Factory Design Pattern
List of Design Patterns