Bridge Design Pattern

The Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation, allowing both to vary independently. Think of it as building a bridge between two hierarchies—one for abstraction and one for implementation—so they can evolve separately without affecting each other.
Imagine a remote control (abstraction) that can work with any TV brand (implementation). You can add new remote features or support new TV brands independently, without modifying existing code. That's the power of the Bridge Pattern!
The Problem
Suppose you're building a graphics application that needs to draw shapes on different platforms (Windows, Linux, Mac). Without the Bridge Pattern, you'd end up with an explosion of classes:
// Without Bridge Pattern - Class Explosion!
class WindowsCircle extends Circle { }
class LinuxCircle extends Circle { }
class MacCircle extends Circle { }
class WindowsSquare extends Square { }
class LinuxSquare extends Square { }
class MacSquare extends Square { }
// And it keeps growing... 😱
Every time you add a new shape or platform, you need to create multiple new classes. This violates the Open/Closed Principle and becomes a maintenance nightmare.
The Solution: Bridge Pattern
The Bridge Pattern separates the abstraction (Shape) from the implementation (Platform), allowing them to vary independently.
Key Components
Abstraction: Defines the high-level interface and maintains a reference to the implementation
Refined Abstraction: Extends the abstraction with additional features
Implementation: Defines the interface for implementation classes
Concrete Implementation: Provides specific implementations
Real-World Implementation
Example 1: Shape Drawing System
// Implementation Interface - The "Bridge"
public interface DrawingAPI {
void drawCircle(double x, double y, double radius);
void drawRectangle(double x, double y, double width, double height);
}
// Concrete Implementation A - Windows
public class WindowsDrawingAPI implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.println("Windows: Drawing circle at (" + x + "," + y +
") with radius " + radius);
}
@Override
public void drawRectangle(double x, double y, double width, double height) {
System.out.println("Windows: Drawing rectangle at (" + x + "," + y + ") with width " + width + " and height " + height);
}
}
// Concrete Implementation B - Linux
public class LinuxDrawingAPI implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.println("Linux: Drawing circle at (" + x + "," + y + ") with radius " + radius);
}
@Override
public void drawRectangle(double x, double y, double width, double height) {
System.out.println("Linux: Drawing rectangle at (" + x + "," + y + ") with width " + width + " and height " + height);
}
}
// Abstraction - Shape
public abstract class Shape {
protected DrawingAPI drawingAPI;
protected Shape(DrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
public abstract void draw();
public abstract void resize(double factor);
}
// Refined Abstraction - Circle
public class Circle extends Shape {
private double x, y, radius;
public Circle(double x, double y, double radius, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawingAPI.drawCircle(x, y, radius);
}
@Override
public void resize(double factor) {
radius *= factor;
System.out.println("Circle resized to radius: " + radius);
}
}
// Refined Abstraction - Rectangle
public class Rectangle extends Shape {
private double x, y, width, height;
public Rectangle(double x, double y, double width, double height,
DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public void draw() {
drawingAPI.drawRectangle(x, y, width, height);
}
@Override
public void resize(double factor) {
width *= factor;
height *= factor;
System.out.println("Rectangle resized to " + width + "x" + height);
}
}
// Client Code
public class BridgePatternDemo {
public static void main(String[] args) {
// Create shapes with different implementations
Shape windowsCircle = new Circle(5, 10, 15, new WindowsDrawingAPI());
Shape linuxRectangle = new Rectangle(20, 30, 40, 50, new LinuxDrawingAPI());
windowsCircle.draw();
windowsCircle.resize(2);
windowsCircle.draw();
System.out.println();
linuxRectangle.draw();
linuxRectangle.resize(1.5);
linuxRectangle.draw();
}
}
/* Output:
Windows: Drawing circle at (5.0,10.0) with radius 15.0
Circle resized to radius: 30.0
Windows: Drawing circle at (5.0,10.0) with radius 30.0
Linux: Drawing rectangle at (20.0,30.0) with width 40.0 and height 50.0
Rectangle resized to 60.0x75.0
Linux: Drawing rectangle at (20.0,30.0) with width 60.0 and height 75.0
*/
Example 2: Remote Control and Devices
// Implementation Interface
public interface Device {
void turnOn();
void turnOff();
void setVolume(int volume);
int getVolume();
void setChannel(int channel);
int getChannel();
}
// Concrete Implementation - TV
public class TV implements Device {
private boolean on = false;
private int volume = 30;
private int channel = 1;
@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 = Math.max(0, Math.min(100, volume));
System.out.println("TV volume set to: " + this.volume);
}
@Override
public int getVolume() {
return volume;
}
@Override
public void setChannel(int channel) {
this.channel = channel;
System.out.println("TV channel set to: " + channel);
}
@Override
public int getChannel() {
return channel;
}
}
// Concrete Implementation - Radio
public class Radio implements Device {
private boolean on = false;
private int volume = 20;
private int channel = 90;
@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 = Math.max(0, Math.min(100, volume));
System.out.println("Radio volume set to: " + this.volume);
}
@Override
public int getVolume() {
return volume;
}
@Override
public void setChannel(int channel) {
this.channel = channel;
System.out.println("Radio frequency set to: " + channel + " FM");
}
@Override
public int getChannel() {
return channel;
}
}
// Abstraction - Remote Control
public abstract class RemoteControl {
protected Device device;
public RemoteControl(Device device) {
this.device = device;
}
public void togglePower() {
System.out.println("Remote: Toggling power");
// Implementation would check device state
device.turnOn();
}
public void volumeUp() {
System.out.println("Remote: Volume up");
device.setVolume(device.getVolume() + 10);
}
public void volumeDown() {
System.out.println("Remote: Volume down");
device.setVolume(device.getVolume() - 10);
}
public void channelUp() {
System.out.println("Remote: Channel up");
device.setChannel(device.getChannel() + 1);
}
public void channelDown() {
System.out.println("Remote: Channel down");
device.setChannel(device.getChannel() - 1);
}
}
// Refined Abstraction - Advanced Remote
public class AdvancedRemote extends RemoteControl {
public AdvancedRemote(Device device) {
super(device);
}
public void mute() {
System.out.println("Advanced Remote: Muting");
device.setVolume(0);
}
public void setChannelDirect(int channel) {
System.out.println("Advanced Remote: Setting channel directly");
device.setChannel(channel);
}
}
// Client Code
public class RemoteDemo {
public static void main(String[] args) {
Device tv = new TV();
RemoteControl basicRemote = new RemoteControl(tv) {};
basicRemote.togglePower();
basicRemote.volumeUp();
basicRemote.channelUp();
System.out.println("\n--- Switching to Advanced Remote ---\n");
Device radio = new Radio();
AdvancedRemote advancedRemote = new AdvancedRemote(radio);
advancedRemote.togglePower();
advancedRemote.volumeUp();
advancedRemote.setChannelDirect(95);
advancedRemote.mute();
}
}
Workflow Diagram
Two Hierarchies Working Together
Real-World Use Cases
When to Use the Bridge Pattern
✅ Use When
Multiple Dimensions of Variation: You have two or more orthogonal dimensions that can vary independently
Avoid Class Explosion: Inheritance would lead to exponential growth of classes
Runtime Implementation Switching: You need to switch implementations at runtime
Platform Independence: Building cross-platform applications
Hide Implementation Details: You want to completely hide implementation from clients
❌ Avoid When
Single Dimension: You only have one dimension of variation
Simple Hierarchy: The abstraction-implementation relationship is straightforward
No Variation Expected: Neither abstraction nor implementation will change
Over-Engineering: The added complexity isn't justified
Benefits
Decoupling: Separates interface from implementation
Extensibility: Add new abstractions and implementations independently
Flexibility: Switch implementations at runtime
Single Responsibility: Each class has a clear, focused purpose
Open/Closed Principle: Extend without modifying existing code
Drawbacks
Complexity: Adds extra layers of abstraction
Overhead: More classes and interfaces to manage
Learning Curve: Can be harder to understand initially
Overkill: May be unnecessary for simple scenarios
Bridge vs Adapter
Advanced Example: Notification System
// Implementation Interface
public interface MessageSender {
void sendMessage(String message, String recipient);
}
// Concrete Implementation - Email
public class EmailSender implements MessageSender {
@Override
public void sendMessage(String message, String recipient) {
System.out.println("Sending Email to: " + recipient);
System.out.println("Message: " + message);
System.out.println("---");
}
}
// Concrete Implementation - SMS
public class SMSSender implements MessageSender {
@Override
public void sendMessage(String message, String recipient) {
System.out.println("Sending SMS to: " + recipient);
System.out.println("Message: " + message);
System.out.println("---");
}
}
// Concrete Implementation - Push Notification
public class PushNotificationSender implements MessageSender {
@Override
public void sendMessage(String message, String recipient) {
System.out.println("Sending Push Notification to: " + recipient);
System.out.println("Message: " + message);
System.out.println("---");
}
}
// Abstraction - Notification
public abstract class Notification {
protected MessageSender sender;
public Notification(MessageSender sender) {
this.sender = sender;
}
public abstract void send(String message, String recipient);
}
// Refined Abstraction - Urgent Notification
public class UrgentNotification extends Notification {
public UrgentNotification(MessageSender sender) {
super(sender);
}
@Override
public void send(String message, String recipient) {
String urgentMessage = "URGENT: " + message;
sender.sendMessage(urgentMessage, recipient);
}
}
// Refined Abstraction - Regular Notification
public class RegularNotification extends Notification {
public RegularNotification(MessageSender sender) {
super(sender);
}
@Override
public void send(String message, String recipient) {
sender.sendMessage(message, recipient);
}
}
// Refined Abstraction - Scheduled Notification
public class ScheduledNotification extends Notification {
private String scheduleTime;
public ScheduledNotification(MessageSender sender, String scheduleTime) {
super(sender);
this.scheduleTime = scheduleTime;
}
@Override
public void send(String message, String recipient) {
String scheduledMessage = "[Scheduled for " + scheduleTime + "] " + message;
sender.sendMessage(scheduledMessage, recipient);
}
}
// Client Code
public class NotificationDemo {
public static void main(String[] args) {
// Urgent notification via Email
Notification urgentEmail = new UrgentNotification(new EmailSender());
urgentEmail.send("Server is down!", "admin@example.com");
// Regular notification via SMS
Notification regularSMS = new RegularNotification(new SMSSender());
regularSMS.send("Your order has been shipped", "+1234567890");
// Scheduled notification via Push
Notification scheduledPush = new ScheduledNotification(
new PushNotificationSender(), "2026-06-19 09:00"
);
scheduledPush.send("Meeting reminder", "user123");
// Easy to switch implementations
System.out.println("\n--- Switching to different sender ---\n");
Notification urgentSMS = new UrgentNotification(new SMSSender());
urgentSMS.send("Critical alert!", "+1234567890");
}
}
Bridge vs Adapter
Pattern Comparison
| Aspect | Bridge | Adapter | Strategy |
|---|---|---|---|
| Intent | Decouple abstraction from impl | Make incompatible interfaces work | Select algorithm at runtime |
| When Designed | Upfront design | After code exists | Upfront or refactoring |
| Structure | Two hierarchies | Single wrapper | Single hierarchy |
| Flexibility | Both sides vary independently | Adapts one interface to another | Swap algorithms |
| Use Case | Cross-platform apps | Legacy integration | Different behaviors |
Best Practices
Favor Composition Over Inheritance: Use Bridge instead of deep inheritance hierarchies
Keep Abstractions Clean: Don't leak implementation details into abstraction
Use Dependency Injection: Pass implementation through constructor
Document Both Hierarchies: Clearly explain abstraction and implementation sides
Consider Factory Pattern: Use factories to create proper combinations
Test Independently: Test abstractions and implementations separately
Conclusion
The Bridge Pattern is your architectural blueprint for managing complexity when you have multiple dimensions of variation. By separating abstraction from implementation, you create flexible, maintainable code that can evolve independently on both sides of the bridge.
Remember: Build bridges, not monoliths! 🌉
🎯 Key Takeaway
The Bridge Pattern is about separation of concerns at the architectural level. When you have two dimensions that need to vary independently, build a bridge between them!
A Bridge and an Adapter walked into a bar. The bartender asked, "What's the difference?" The Bridge replied, "I was designed to separate concerns from the start. This guy just patches things together after the fact!" 🌉😄
Happy Bridging! 🌉✨




