The Bridge pattern is a structural design pattern used to decouple an abstraction from its implementation, so that the two can vary independently. Instead of binding the abstraction to a specific implementation at compile time (via inheritance), the bridge pattern uses composition to link them at runtime. This pattern is particularly useful when both the abstraction and implementation may evolve independently, or when a system needs to support multiple variations of each.

Applicability

  • When you want to avoid a permanent binding between an abstraction and its implementation.
  • When both the abstraction and its implementation may be extended independently
  • When you want to hide implementation details from the client
  • When there are multiple dimensions of variation and using inheritance would result in a complex class hierarchy (i.e., class explosion)

Pros and Cons

Advantages

  • Decouples abstraction and implementation
    • Changes in implementation do not require changes in the abstraction and vice versa.
  • Improves flexibility and extensibility
    • Both abstraction and implementation can be extended independently.
  • Reduces class explosion
    • Avoids creating many classes for all combinations of abstraction and implementation.
  • Promotes composition over inheritance
    • More dynamic and reusable than static inheritance hierarchies.

Drawbacks

  • Increased complexity
    • Requires additional indirection and more classes/interfaces.
  • Less straightforward for simple scenarios
    • May be overkill if there’s no significant variation on both sides.

Approach

  1. Define the Abstraction interface or class that clients will interact with.
  2. Create a Refined Abstraction that extends the base abstraction to add specific features.
  3. Define an Implementor interface that declares the platform-specific operations.
  4. Create Concrete Implementors that implement the Implementor interface.
  5. The Abstraction contains a reference to an Implementor and delegates work to it.
classDiagram
    class Abstraction {
        - implementor: Implementor
        + operation()
    }
    class RefinedAbstraction {
        + operation()
        + extendedOperation()
    }
    class Implementor {
        <<interface>>
        + operationImpl()
    }
    class ConcreteImplementorA {
        + operationImpl()
    }
    class ConcreteImplementorB {
        + operationImpl()
    }
    Abstraction <|-- RefinedAbstraction
    Abstraction o-- Implementor
    Implementor <|.. ConcreteImplementorA
    Implementor <|.. ConcreteImplementorB

Component

  • Abstraction
    • Defines the interface and maintains a reference to the implementor.
  • Refined Abstraction
    • Extends the abstraction interface and adds more features.
  • Implementor
    • Defines the interface for implementation classes.
  • Concrete Implementor
    • Provides concrete implementation of the operations defined in the Implementor.

Example

Suppose we have different types of remote controls (abstractions) and different types of devices (implementors), and we want them to work together flexibly.

Abstraction

abstract class RemoteControl {
    protected Device device;
 
    public RemoteControl(Device device) {
        this.device = device;
    }
 
    public abstract void togglePower();
    public abstract void volumeUp();
    public abstract void volumeDown();
}

Refined abstraction

class BasicRemote extends RemoteControl {
    private boolean isOn = false;
    private int volume = 10;
 
    public BasicRemote(Device device) {
        super(device);
    }
 
    @Override
    public void togglePower() {
        if (isOn) {
            device.turnOff();
        } else {
            device.turnOn();
        }
        isOn = !isOn;
    }
 
    @Override
    public void volumeUp() {
        volume += 1;
        device.setVolume(volume);
    }
 
    @Override
    public void volumeDown() {
        volume -= 1;
        device.setVolume(volume);
    }
}

Implementor

interface Device {
    void turnOn();
    void turnOff();
    void setVolume(int level);
}

Concrete implementor

class TV implements Device {
    @Override
    public void turnOn() {
        System.out.println("TV turned ON");
    }
 
    @Override
    public void turnOff() {
        System.out.println("TV turned OFF");
    }
 
    @Override
    public void setVolume(int level) {
        System.out.println("TV volume set to " + level);
    }
}
class Radio implements Device {
    @Override
    public void turnOn() {
        System.out.println("Radio turned ON");
    }
 
    @Override
    public void turnOff() {
        System.out.println("Radio turned OFF");
    }
 
    @Override
    public void setVolume(int level) {
        System.out.println("Radio volume set to " + level);
    }
}

Client

public class BridgeExample {
    public static void main(String[] args) {
        Device tv = new TV();
        RemoteControl remote = new BasicRemote(tv);
        remote.togglePower();
        remote.volumeUp();
        remote.volumeDown();
 
        Device radio = new Radio();
        RemoteControl radioRemote = new BasicRemote(radio);
        radioRemote.togglePower();
        radioRemote.volumeUp();
    }
}
TV turned ON
TV volume set to 11
TV volume set to 10
Radio turned ON
Radio volume set to 11

Back to parent page: Structural Patterns

Design_Pattern Structural_Design_Patterns Bridge_Pattern SOFT3202