The Bridge Pattern is a structural design pattern that helps decouple abstraction from implementation, allowing them to evolve independently.
This is particularly useful when dealing with multiple dimensions of variability in our system. Instead of having a complex hierarchy of subclasses, we use composition to separate concerns, making our code more maintainable and scalable.
In this post, we'll explore the Bridge Pattern using an easy-to-understand example with different devices and remote controls. ๐บ๐ฎ
Why Use the Bridge Pattern?
โ
Reduces Class Explosion โ Avoids deep inheritance trees when dealing with multiple variations.
โ
Promotes Composition Over Inheritance โ Favors flexibility by allowing independent changes to abstraction and implementation.
โ
Improves Maintainability โ Enhances readability and separation of concerns.
Problem Without the Bridge Pattern
Imagine we have different types of devices (like TV and Radio) and different remote controls (like Basic Remote and Advanced Remote).
A bad approach would be using inheritance to create a subclass for each combination, like:
-
BasicTVRemote
-
AdvancedTVRemote
-
BasicRadioRemote
-
AdvancedRadioRemote
As we add more device types or remote types, our class hierarchy grows exponentially! ๐
Example Without the Bridge Pattern (Using Inheritance)
// Parent class for all remotes
public abstract class Remote {
public abstract void turnOn();
public abstract void turnOff();
}
// Concrete class for Basic TV Remote
public class BasicTVRemote extends Remote {
@Override
public void turnOn() {
System.out.println("Turning on the TV.");
}
@Override
public void turnOff() {
System.out.println("Turning off the TV.");
}
}
// Concrete class for Advanced TV Remote
public class AdvancedTVRemote extends BasicTVRemote {
public void mute() {
System.out.println("Muting the TV.");
}
}
// Concrete class for Basic Radio Remote
public class BasicRadioRemote extends Remote {
@Override
public void turnOn() {
System.out.println("Turning on the Radio.");
}
@Override
public void turnOff() {
System.out.println("Turning off the Radio.");
}
}
// Concrete class for Advanced Radio Remote
public class AdvancedRadioRemote extends BasicRadioRemote {
public void mute() {
System.out.println("Muting the Radio.");
}
}
// Main class
public class WithoutBridgeExample {
public static void main(String[] args) {
BasicTVRemote basicTV = new BasicTVRemote();
AdvancedTVRemote advancedTV = new AdvancedTVRemote();
BasicRadioRemote basicRadio = new BasicRadioRemote();
AdvancedRadioRemote advancedRadio = new AdvancedRadioRemote();
basicTV.turnOn();
advancedTV.mute();
basicRadio.turnOff();
advancedRadio.mute();
}
}
Problems with This Approach
๐จ Class Explosion โ Every new remote type requires new subclasses.
๐จ Hard to Maintain โ Changes in the Remote logic require modifications in multiple classes.
๐จ Tight Coupling โ Device behavior is tightly linked to Remote behavior.
Solution: Using the Bridge Pattern
Instead of relying on inheritance, we use composition:
- We separate Devices (TV, Radio, etc.) from Remotes (Basic, Advanced, etc.).
- Remotes hold a reference to a Device, allowing independent variations.
Step 1: Create the Device Interface
Each device must implement basic functionalities like turning on/off and changing volume.
public interface Device {
void turnOn();
void turnOff();
void setVolume(int volume);
boolean isOn();
}
Step 2: Implement Different Devices (TV and Radio)
public class TV implements Device {
private boolean on = false;
private int volume = 50;
@Override
public void turnOn() {
on = true;
System.out.println("TV is now ON");
}
@Override
public void turnOff() {
on = false;
System.out.println("TV is now OFF");
}
@Override
public void setVolume(int volume) {
this.volume = volume;
System.out.println("TV volume set to " + volume);
}
@Override
public boolean isOn() {
return on;
}
}
public class Radio implements Device {
private boolean on = false;
private int volume = 30;
@Override
public void turnOn() {
on = true;
System.out.println("Radio is now ON");
}
@Override
public void turnOff() {
on = false;
System.out.println("Radio is now OFF");
}
@Override
public void setVolume(int volume) {
this.volume = volume;
System.out.println("Radio volume set to " + volume);
}
@Override
public boolean isOn() {
return on;
}
}
Step 3: Create the Remote Control Abstraction
Now, we define an abstract class RemoteControl
, which uses composition to interact with a Device
.
public class RemoteControl {
protected Device device;
public RemoteControl(Device device) {
this.device = device;
}
public void togglePower() {
if (device.isOn()) {
device.turnOff();
} else {
device.turnOn();
}
}
public void volumeUp() {
device.setVolume(50);
}
public void volumeDown() {
device.setVolume(10);
}
}
Step 4: Extend the Remote Control with More Functionality
Let's create an Advanced Remote that can also mute the device.
public class AdvancedRemoteControl extends RemoteControl {
public AdvancedRemoteControl(Device device) {
super(device);
}
public void mute() {
System.out.println("Muting the device");
device.setVolume(0);
}
}
Step 5: Using the Bridge Pattern in the Main Program
public class BridgePatternExample {
public static void main(String[] args) {
Device tv = new TV();
Device radio = new Radio();
RemoteControl basicRemote = new RemoteControl(tv);
AdvancedRemoteControl advancedRemote = new AdvancedRemoteControl(radio);
System.out.println("Using Basic Remote with TV:");
basicRemote.togglePower();
basicRemote.volumeUp();
System.out.println("\nUsing Advanced Remote with Radio:");
advancedRemote.togglePower();
advancedRemote.mute();
}
}
Pros of the Bridge Pattern
โ
Decouples abstraction from implementation โ Devices and Remotes can evolve separately.
โ
Improves Maintainability โ Changes in one part of the code donโt affect the other.
โ
Promotes Code Reusability โ You can mix and match different implementations.
Conclusion
The Bridge Pattern is a great solution when you have multiple variations of both abstractions and implementations. By using composition over inheritance, we gain flexibility, scalability, and better code organization.
Top comments (0)