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
- Define the Abstraction interface or class that clients will interact with.
- Create a Refined Abstraction that extends the base abstraction to add specific features.
- Define an Implementor interface that declares the platform-specific operations.
- Create Concrete Implementors that implement the
Implementor
interface. - The
Abstraction
contains a reference to anImplementor
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
.
- Provides concrete implementation of the operations defined in the
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