Skip to main content

Command Palette

Search for a command to run...

Bridge Design Pattern

Updated
10 min read
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

  1. Abstraction: Defines the high-level interface and maintains a reference to the implementation

  2. Refined Abstraction: Extends the abstraction with additional features

  3. Implementation: Defines the interface for implementation classes

  4. 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

  1. Multiple Dimensions of Variation: You have two or more orthogonal dimensions that can vary independently

  2. Avoid Class Explosion: Inheritance would lead to exponential growth of classes

  3. Runtime Implementation Switching: You need to switch implementations at runtime

  4. Platform Independence: Building cross-platform applications

  5. Hide Implementation Details: You want to completely hide implementation from clients

❌ Avoid When

  1. Single Dimension: You only have one dimension of variation

  2. Simple Hierarchy: The abstraction-implementation relationship is straightforward

  3. No Variation Expected: Neither abstraction nor implementation will change

  4. Over-Engineering: The added complexity isn't justified

Benefits

  1. Decoupling: Separates interface from implementation

  2. Extensibility: Add new abstractions and implementations independently

  3. Flexibility: Switch implementations at runtime

  4. Single Responsibility: Each class has a clear, focused purpose

  5. Open/Closed Principle: Extend without modifying existing code

Drawbacks

  1. Complexity: Adds extra layers of abstraction

  2. Overhead: More classes and interfaces to manage

  3. Learning Curve: Can be harder to understand initially

  4. 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

  1. Favor Composition Over Inheritance: Use Bridge instead of deep inheritance hierarchies

  2. Keep Abstractions Clean: Don't leak implementation details into abstraction

  3. Use Dependency Injection: Pass implementation through constructor

  4. Document Both Hierarchies: Clearly explain abstraction and implementation sides

  5. Consider Factory Pattern: Use factories to create proper combinations

  6. 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! 🌉✨