Bridge Pattern
Bridge Pattern: Separating Abstraction from Implementation
Section titled “Bridge Pattern: Separating Abstraction from Implementation”Now let’s explore the Bridge Pattern - a powerful structural design pattern that decouples abstraction from implementation.
Why Bridge Pattern?
Section titled “Why Bridge Pattern?”Imagine you’re building a remote control for your TV. You want to support different types of TVs (Samsung, Sony, LG) and different types of remotes (Basic, Advanced, Smart). Without Bridge Pattern, you’d need a class for every combination (BasicSamsungRemote, AdvancedSamsungRemote, SmartSonyRemote, etc.) - that’s a lot of classes! The Bridge Pattern solves this by separating the remote (abstraction) from the TV (implementation).
The Bridge Pattern decouples an abstraction from its implementation so that the two can vary independently. It uses composition instead of inheritance to bridge the abstraction and implementation.
What’s the Use of Bridge Pattern?
Section titled “What’s the Use of Bridge Pattern?”The Bridge Pattern is useful when:
- You want to avoid permanent binding - Between abstraction and implementation
- Abstraction and implementation should be extensible - Independently by subclassing
- Changes in implementation - Should not affect clients
- Hide implementation details - From clients
- Multiple implementations - Need to share the same abstraction
What Happens If We Don’t Use Bridge Pattern?
Section titled “What Happens If We Don’t Use Bridge Pattern?”Without the Bridge Pattern, you might:
- Exponential class explosion - Need classes for every combination (MxN classes)
- Tight coupling - Abstraction and implementation are tightly bound
- Hard to extend - Adding new abstraction or implementation requires many changes
- Violate Open/Closed Principle - Need to modify existing code for new combinations
- Code duplication - Similar code repeated across multiple classes
Simple Example: The Remote Control
Section titled “Simple Example: The Remote Control”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Bridge Pattern works in practice - showing how abstraction and implementation interact:
The Problem
Section titled “The Problem”You’re building a remote control system. You have different types of remotes (Basic, Advanced) and different types of TVs (Samsung, Sony). Without Bridge Pattern:
# ❌ Without Bridge Pattern - Class explosion!
class BasicSamsungRemote: """Basic remote for Samsung TV""" def power_on(self): print("Samsung TV: Turning on...")
def power_off(self): print("Samsung TV: Turning off...")
def volume_up(self): print("Samsung TV: Volume up")
class AdvancedSamsungRemote: """Advanced remote for Samsung TV""" def power_on(self): print("Samsung TV: Turning on...")
def power_off(self): print("Samsung TV: Turning off...")
def volume_up(self): print("Samsung TV: Volume up")
def mute(self): print("Samsung TV: Muted")
class BasicSonyRemote: """Basic remote for Sony TV""" def power_on(self): print("Sony TV: Turning on...")
def power_off(self): print("Sony TV: Turning off...")
def volume_up(self): print("Sony TV: Volume up")
class AdvancedSonyRemote: """Advanced remote for Sony TV""" def power_on(self): print("Sony TV: Turning on...")
def power_off(self): print("Sony TV: Turning off...")
def volume_up(self): print("Sony TV: Volume up")
def mute(self): print("Sony TV: Muted")
# Problems:# - Need 2 remotes × 2 TVs = 4 classes (and more as you add types!)# - Code duplication - same remote logic repeated# - Hard to add new remote or TV type# - Violates Open/Closed Principle// ❌ Without Bridge Pattern - Class explosion!
public class BasicSamsungRemote { // Basic remote for Samsung TV public void powerOn() { System.out.println("Samsung TV: Turning on..."); }
public void powerOff() { System.out.println("Samsung TV: Turning off..."); }
public void volumeUp() { System.out.println("Samsung TV: Volume up"); }}
public class AdvancedSamsungRemote { // Advanced remote for Samsung TV public void powerOn() { System.out.println("Samsung TV: Turning on..."); }
public void powerOff() { System.out.println("Samsung TV: Turning off..."); }
public void volumeUp() { System.out.println("Samsung TV: Volume up"); }
public void mute() { System.out.println("Samsung TV: Muted"); }}
public class BasicSonyRemote { // Basic remote for Sony TV public void powerOn() { System.out.println("Sony TV: Turning on..."); }
public void powerOff() { System.out.println("Sony TV: Turning off..."); }
public void volumeUp() { System.out.println("Sony TV: Volume up"); }}
public class AdvancedSonyRemote { // Advanced remote for Sony TV public void powerOn() { System.out.println("Sony TV: Turning on..."); }
public void powerOff() { System.out.println("Sony TV: Turning off..."); }
public void volumeUp() { System.out.println("Sony TV: Volume up"); }
public void mute() { System.out.println("Sony TV: Muted"); }}
// Problems:// - Need 2 remotes × 2 TVs = 4 classes (and more as you add types!)// - Code duplication - same remote logic repeated// - Hard to add new remote or TV type// - Violates Open/Closed Principle// ❌ Without Bridge Pattern - Class explosion!
class BasicSamsungRemote { /** Basic remote for Samsung TV */ powerOn(): void { console.log("Samsung TV: Turning on..."); }
powerOff(): void { console.log("Samsung TV: Turning off..."); }
volumeUp(): void { console.log("Samsung TV: Volume up"); }}
class AdvancedSamsungRemote { /** Advanced remote for Samsung TV */ powerOn(): void { console.log("Samsung TV: Turning on..."); }
powerOff(): void { console.log("Samsung TV: Turning off..."); }
volumeUp(): void { console.log("Samsung TV: Volume up"); }
mute(): void { console.log("Samsung TV: Muted"); }}
class BasicSonyRemote { /** Basic remote for Sony TV */ powerOn(): void { console.log("Sony TV: Turning on..."); }
powerOff(): void { console.log("Sony TV: Turning off..."); }
volumeUp(): void { console.log("Sony TV: Volume up"); }}
class AdvancedSonyRemote { /** Advanced remote for Sony TV */ powerOn(): void { console.log("Sony TV: Turning on..."); }
powerOff(): void { console.log("Sony TV: Turning off..."); }
volumeUp(): void { console.log("Sony TV: Volume up"); }
mute(): void { console.log("Sony TV: Muted"); }}
// Problems:// - Need 2 remotes × 2 TVs = 4 classes (and more as you add types!)// - Code duplication - same remote logic repeated// - Hard to add new remote or TV type// - Violates Open/Closed Principle// ❌ Without Bridge Pattern - Class explosion!
#include <iostream>
class BasicSamsungRemote {public: // Basic remote for Samsung TV void powerOn() { std::cout << "Samsung TV: Turning on..." << std::endl; }
void powerOff() { std::cout << "Samsung TV: Turning off..." << std::endl; }
void volumeUp() { std::cout << "Samsung TV: Volume up" << std::endl; }};
class AdvancedSamsungRemote {public: // Advanced remote for Samsung TV void powerOn() { std::cout << "Samsung TV: Turning on..." << std::endl; }
void powerOff() { std::cout << "Samsung TV: Turning off..." << std::endl; }
void volumeUp() { std::cout << "Samsung TV: Volume up" << std::endl; }
void mute() { std::cout << "Samsung TV: Muted" << std::endl; }};
class BasicSonyRemote {public: // Basic remote for Sony TV void powerOn() { std::cout << "Sony TV: Turning on..." << std::endl; }
void powerOff() { std::cout << "Sony TV: Turning off..." << std::endl; }
void volumeUp() { std::cout << "Sony TV: Volume up" << std::endl; }};
class AdvancedSonyRemote {public: // Advanced remote for Sony TV void powerOn() { std::cout << "Sony TV: Turning on..." << std::endl; }
void powerOff() { std::cout << "Sony TV: Turning off..." << std::endl; }
void volumeUp() { std::cout << "Sony TV: Volume up" << std::endl; }
void mute() { std::cout << "Sony TV: Muted" << std::endl; }};
// Problems:// - Need 2 remotes × 2 TVs = 4 classes (and more as you add types!)// - Code duplication - same remote logic repeated// - Hard to add new remote or TV type// - Violates Open/Closed Principle// ❌ Without Bridge Pattern - Class explosion!
using System;
public class BasicSamsungRemote{ // Basic remote for Samsung TV public void PowerOn() { Console.WriteLine("Samsung TV: Turning on..."); }
public void PowerOff() { Console.WriteLine("Samsung TV: Turning off..."); }
public void VolumeUp() { Console.WriteLine("Samsung TV: Volume up"); }}
public class AdvancedSamsungRemote{ // Advanced remote for Samsung TV public void PowerOn() { Console.WriteLine("Samsung TV: Turning on..."); }
public void PowerOff() { Console.WriteLine("Samsung TV: Turning off..."); }
public void VolumeUp() { Console.WriteLine("Samsung TV: Volume up"); }
public void Mute() { Console.WriteLine("Samsung TV: Muted"); }}
public class BasicSonyRemote{ // Basic remote for Sony TV public void PowerOn() { Console.WriteLine("Sony TV: Turning on..."); }
public void PowerOff() { Console.WriteLine("Sony TV: Turning off..."); }
public void VolumeUp() { Console.WriteLine("Sony TV: Volume up"); }}
public class AdvancedSonyRemote{ // Advanced remote for Sony TV public void PowerOn() { Console.WriteLine("Sony TV: Turning on..."); }
public void PowerOff() { Console.WriteLine("Sony TV: Turning off..."); }
public void VolumeUp() { Console.WriteLine("Sony TV: Volume up"); }
public void Mute() { Console.WriteLine("Sony TV: Muted"); }}
// Problems:// - Need 2 remotes × 2 TVs = 4 classes (and more as you add types!)// - Code duplication - same remote logic repeated// - Hard to add new remote or TV type// - Violates Open/Closed Principle// ❌ Without Bridge Pattern - Struct explosion!
package main
import "fmt"
type BasicSamsungRemote struct{}
func (r *BasicSamsungRemote) PowerOn() { fmt.Println("Samsung TV: Turning on...") }func (r *BasicSamsungRemote) PowerOff() { fmt.Println("Samsung TV: Turning off...") }func (r *BasicSamsungRemote) VolumeUp() { fmt.Println("Samsung TV: Volume up") }
type AdvancedSamsungRemote struct{}
func (r *AdvancedSamsungRemote) PowerOn() { fmt.Println("Samsung TV: Turning on...") }func (r *AdvancedSamsungRemote) PowerOff() { fmt.Println("Samsung TV: Turning off...") }func (r *AdvancedSamsungRemote) VolumeUp() { fmt.Println("Samsung TV: Volume up") }func (r *AdvancedSamsungRemote) Mute() { fmt.Println("Samsung TV: Muted") }
type BasicSonyRemote struct{}
func (r *BasicSonyRemote) PowerOn() { fmt.Println("Sony TV: Turning on...") }func (r *BasicSonyRemote) PowerOff() { fmt.Println("Sony TV: Turning off...") }func (r *BasicSonyRemote) VolumeUp() { fmt.Println("Sony TV: Volume up") }
type AdvancedSonyRemote struct{}
func (r *AdvancedSonyRemote) PowerOn() { fmt.Println("Sony TV: Turning on...") }func (r *AdvancedSonyRemote) PowerOff() { fmt.Println("Sony TV: Turning off...") }func (r *AdvancedSonyRemote) VolumeUp() { fmt.Println("Sony TV: Volume up") }func (r *AdvancedSonyRemote) Mute() { fmt.Println("Sony TV: Muted") }
// Problems:// - Need 2 remotes × 2 TVs = 4 structs (and more as you add types!)// - Code duplication - same remote logic repeated// - Hard to add new remote or TV type// - Violates Open/Closed Principle// The Problemstruct RedCircle;struct BlueCircle;struct RedSquare;struct BlueSquare;impl RedCircle { fn draw(&self) { println!("red circle"); }}impl BlueSquare { fn draw(&self) { println!("blue square"); }}Problems:
- Exponential class explosion - M remotes × N TVs = M×N classes
- Code duplication - Same remote logic repeated for each TV
- Hard to extend - Adding new remote or TV requires many new classes
- Violates Open/Closed Principle
The Solution: Bridge Pattern
Section titled “The Solution: Bridge Pattern”Class Structure
Section titled “Class Structure”from abc import ABC, abstractmethod
# Step 1: Define the implementation interface (TV)class TV(ABC): """Implementation interface - how TVs work"""
@abstractmethod def turn_on(self) -> None: """Turn on the TV""" pass
@abstractmethod def turn_off(self) -> None: """Turn off the TV""" pass
@abstractmethod def set_volume(self, level: int) -> None: """Set volume level (0-100)""" pass
# Step 2: Implement concrete TV classesclass SamsungTV(TV): """Samsung TV implementation"""
def __init__(self): self.volume = 0 self.is_on = False
def turn_on(self) -> None: self.is_on = True print("Samsung TV: Turning on...")
def turn_off(self) -> None: self.is_on = False print("Samsung TV: Turning off...")
def set_volume(self, level: int) -> None: self.volume = max(0, min(100, level)) print(f"Samsung TV: Volume set to {self.volume}")
class SonyTV(TV): """Sony TV implementation"""
def __init__(self): self.volume = 0 self.is_on = False
def turn_on(self) -> None: self.is_on = True print("Sony TV: Turning on...")
def turn_off(self) -> None: self.is_on = False print("Sony TV: Turning off...")
def set_volume(self, level: int) -> None: self.volume = max(0, min(100, level)) print(f"Sony TV: Volume set to {self.volume}")
# Step 3: Define the abstraction (RemoteControl)class RemoteControl: """Abstraction - what user interacts with"""
def __init__(self, tv: TV): self.tv = tv # Bridge to implementation
def power_on(self) -> None: """Turn on the TV""" self.tv.turn_on()
def power_off(self) -> None: """Turn off the TV""" self.tv.turn_off()
def volume_up(self) -> None: """Increase volume""" # Abstraction doesn't know current volume - implementation handles it # This is simplified - in real scenario, you'd track volume in abstraction self.tv.set_volume(50) # Simplified
def volume_down(self) -> None: """Decrease volume""" self.tv.set_volume(30) # Simplified
# Step 4: Extend the abstraction (different remote types)class BasicRemote(RemoteControl): """Basic remote - simple functionality"""
def __init__(self, tv: TV): super().__init__(tv) self.volume_level = 50
def volume_up(self) -> None: """Increase volume""" self.volume_level = min(100, self.volume_level + 10) self.tv.set_volume(self.volume_level)
def volume_down(self) -> None: """Decrease volume""" self.volume_level = max(0, self.volume_level - 10) self.tv.set_volume(self.volume_level)
class AdvancedRemote(RemoteControl): """Advanced remote - extended functionality"""
def __init__(self, tv: TV): super().__init__(tv) self.volume_level = 50 self.is_muted = False
def volume_up(self) -> None: """Increase volume""" if self.is_muted: self.is_muted = False self.volume_level = min(100, self.volume_level + 10) self.tv.set_volume(self.volume_level)
def volume_down(self) -> None: """Decrease volume""" self.volume_level = max(0, self.volume_level - 10) self.tv.set_volume(self.volume_level)
def mute(self) -> None: """Mute/unmute TV""" self.is_muted = not self.is_muted if self.is_muted: self.tv.set_volume(0) print("TV: Muted") else: self.tv.set_volume(self.volume_level) print("TV: Unmuted")
# Usage - Clean and flexible!def main(): # Create TVs (implementations) samsung_tv = SamsungTV() sony_tv = SonyTV()
# Create remotes (abstractions) with different TVs basic_samsung = BasicRemote(samsung_tv) advanced_samsung = AdvancedRemote(samsung_tv) basic_sony = BasicRemote(sony_tv) advanced_sony = AdvancedRemote(sony_tv)
print("=== Basic Remote with Samsung TV ===") basic_samsung.power_on() basic_samsung.volume_up() basic_samsung.volume_down() basic_samsung.power_off()
print("\n=== Advanced Remote with Sony TV ===") advanced_sony.power_on() advanced_sony.volume_up() advanced_sony.mute() advanced_sony.power_off()
print("\n✅ Bridge Pattern allows independent variation of remotes and TVs!")
if __name__ == "__main__": main()// Step 1: Define the implementation interface (TV)interface TV { // Implementation interface - how TVs work void turnOn(); void turnOff(); void setVolume(int level);}
// Step 2: Implement concrete TV classesclass SamsungTV implements TV { // Samsung TV implementation private int volume = 0; private boolean isOn = false;
@Override public void turnOn() { isOn = true; System.out.println("Samsung TV: Turning on..."); }
@Override public void turnOff() { isOn = false; System.out.println("Samsung TV: Turning off..."); }
@Override public void setVolume(int level) { volume = Math.max(0, Math.min(100, level)); System.out.println("Samsung TV: Volume set to " + volume); }}
class SonyTV implements TV { // Sony TV implementation private int volume = 0; private boolean isOn = false;
@Override public void turnOn() { isOn = true; System.out.println("Sony TV: Turning on..."); }
@Override public void turnOff() { isOn = false; System.out.println("Sony TV: Turning off..."); }
@Override public void setVolume(int level) { volume = Math.max(0, Math.min(100, level)); System.out.println("Sony TV: Volume set to " + volume); }}
// Step 3: Define the abstraction (RemoteControl)abstract class RemoteControl { // Abstraction - what user interacts with protected TV tv; // Bridge to implementation
public RemoteControl(TV tv) { this.tv = tv; }
public void powerOn() { // Turn on the TV tv.turnOn(); }
public void powerOff() { // Turn off the TV tv.turnOff(); }
public abstract void volumeUp(); public abstract void volumeDown();}
// Step 4: Extend the abstraction (different remote types)class BasicRemote extends RemoteControl { // Basic remote - simple functionality private int volumeLevel = 50;
public BasicRemote(TV tv) { super(tv); }
@Override public void volumeUp() { // Increase volume volumeLevel = Math.min(100, volumeLevel + 10); tv.setVolume(volumeLevel); }
@Override public void volumeDown() { // Decrease volume volumeLevel = Math.max(0, volumeLevel - 10); tv.setVolume(volumeLevel); }}
class AdvancedRemote extends RemoteControl { // Advanced remote - extended functionality private int volumeLevel = 50; private boolean isMuted = false;
public AdvancedRemote(TV tv) { super(tv); }
@Override public void volumeUp() { // Increase volume if (isMuted) { isMuted = false; } volumeLevel = Math.min(100, volumeLevel + 10); tv.setVolume(volumeLevel); }
@Override public void volumeDown() { // Decrease volume volumeLevel = Math.max(0, volumeLevel - 10); tv.setVolume(volumeLevel); }
public void mute() { // Mute/unmute TV isMuted = !isMuted; if (isMuted) { tv.setVolume(0); System.out.println("TV: Muted"); } else { tv.setVolume(volumeLevel); System.out.println("TV: Unmuted"); } }}
// Usage - Clean and flexible!public class Main { public static void main(String[] args) { // Create TVs (implementations) TV samsungTV = new SamsungTV(); TV sonyTV = new SonyTV();
// Create remotes (abstractions) with different TVs RemoteControl basicSamsung = new BasicRemote(samsungTV); RemoteControl advancedSamsung = new AdvancedRemote(samsungTV); RemoteControl basicSony = new BasicRemote(sonyTV); RemoteControl advancedSony = new AdvancedRemote(sonyTV);
System.out.println("=== Basic Remote with Samsung TV ==="); basicSamsung.powerOn(); basicSamsung.volumeUp(); basicSamsung.volumeDown(); basicSamsung.powerOff();
System.out.println("\n=== Advanced Remote with Sony TV ==="); advancedSony.powerOn(); advancedSony.volumeUp(); ((AdvancedRemote) advancedSony).mute(); advancedSony.powerOff();
System.out.println("\n✅ Bridge Pattern allows independent variation of remotes and TVs!"); }}// Step 1: Define the implementation interface (TV)interface TV { /** Implementation interface - how TVs work */ turnOn(): void; turnOff(): void; setVolume(level: number): void;}
// Step 2: Implement concrete TV classesclass SamsungTV implements TV { /** Samsung TV implementation */ private volume: number = 0; private isOn: boolean = false;
turnOn(): void { this.isOn = true; console.log("Samsung TV: Turning on..."); }
turnOff(): void { this.isOn = false; console.log("Samsung TV: Turning off..."); }
setVolume(level: number): void { this.volume = Math.max(0, Math.min(100, level)); console.log(`Samsung TV: Volume set to ${this.volume}`); }}
class SonyTV implements TV { /** Sony TV implementation */ private volume: number = 0; private isOn: boolean = false;
turnOn(): void { this.isOn = true; console.log("Sony TV: Turning on..."); }
turnOff(): void { this.isOn = false; console.log("Sony TV: Turning off..."); }
setVolume(level: number): void { this.volume = Math.max(0, Math.min(100, level)); console.log(`Sony TV: Volume set to ${this.volume}`); }}
// Step 3: Define the abstraction (RemoteControl)class RemoteControl { /** Abstraction - what user interacts with */ protected tv: TV; // Bridge to implementation
constructor(tv: TV) { this.tv = tv; }
powerOn(): void { /** Turn on the TV */ this.tv.turnOn(); }
powerOff(): void { /** Turn off the TV */ this.tv.turnOff(); }
volumeUp(): void { /** Increase volume */ this.tv.setVolume(50); // Simplified }
volumeDown(): void { /** Decrease volume */ this.tv.setVolume(30); // Simplified }}
// Step 4: Extend the abstraction (different remote types)class BasicRemote extends RemoteControl { /** Basic remote - simple functionality */ private volumeLevel: number = 50;
constructor(tv: TV) { super(tv); }
volumeUp(): void { /** Increase volume */ this.volumeLevel = Math.min(100, this.volumeLevel + 10); this.tv.setVolume(this.volumeLevel); }
volumeDown(): void { /** Decrease volume */ this.volumeLevel = Math.max(0, this.volumeLevel - 10); this.tv.setVolume(this.volumeLevel); }}
class AdvancedRemote extends RemoteControl { /** Advanced remote - extended functionality */ private volumeLevel: number = 50; private isMuted: boolean = false;
constructor(tv: TV) { super(tv); }
volumeUp(): void { /** Increase volume */ if (this.isMuted) { this.isMuted = false; } this.volumeLevel = Math.min(100, this.volumeLevel + 10); this.tv.setVolume(this.volumeLevel); }
volumeDown(): void { /** Decrease volume */ this.volumeLevel = Math.max(0, this.volumeLevel - 10); this.tv.setVolume(this.volumeLevel); }
mute(): void { /** Mute/unmute TV */ this.isMuted = !this.isMuted; if (this.isMuted) { this.tv.setVolume(0); console.log("TV: Muted"); } else { this.tv.setVolume(this.volumeLevel); console.log("TV: Unmuted"); } }}
// Usage - Clean and flexible!function main(): void { // Create TVs (implementations) const samsungTV = new SamsungTV(); const sonyTV = new SonyTV();
// Create remotes (abstractions) with different TVs const basicSamsung = new BasicRemote(samsungTV); const advancedSamsung = new AdvancedRemote(samsungTV); const basicSony = new BasicRemote(sonyTV); const advancedSony = new AdvancedRemote(sonyTV);
console.log("=== Basic Remote with Samsung TV ==="); basicSamsung.powerOn(); basicSamsung.volumeUp(); basicSamsung.volumeDown(); basicSamsung.powerOff();
console.log("\n=== Advanced Remote with Sony TV ==="); advancedSony.powerOn(); advancedSony.volumeUp(); advancedSony.mute(); advancedSony.powerOff();
console.log("\n✅ Bridge Pattern allows independent variation of remotes and TVs!");}
main();#include <iostream>#include <memory>#include <algorithm>
// Step 1: Define the implementation interface (TV)class TV {public: virtual ~TV() = default; // Implementation interface - how TVs work virtual void turnOn() = 0; virtual void turnOff() = 0; virtual void setVolume(int level) = 0;};
// Step 2: Implement concrete TV classesclass SamsungTV : public TV {private: int volume = 0; bool isOn = false;
public: void turnOn() override { isOn = true; std::cout << "Samsung TV: Turning on..." << std::endl; }
void turnOff() override { isOn = false; std::cout << "Samsung TV: Turning off..." << std::endl; }
void setVolume(int level) override { volume = std::max(0, std::min(100, level)); std::cout << "Samsung TV: Volume set to " << volume << std::endl; }};
class SonyTV : public TV {private: int volume = 0; bool isOn = false;
public: void turnOn() override { isOn = true; std::cout << "Sony TV: Turning on..." << std::endl; }
void turnOff() override { isOn = false; std::cout << "Sony TV: Turning off..." << std::endl; }
void setVolume(int level) override { volume = std::max(0, std::min(100, level)); std::cout << "Sony TV: Volume set to " << volume << std::endl; }};
// Step 3: Define the abstraction (RemoteControl)class RemoteControl {protected: std::shared_ptr<TV> tv; // Bridge to implementation
public: RemoteControl(std::shared_ptr<TV> tv) : tv(tv) {} virtual ~RemoteControl() = default;
virtual void powerOn() { // Turn on the TV tv->turnOn(); }
virtual void powerOff() { // Turn off the TV tv->turnOff(); }
virtual void volumeUp() { // Increase volume tv->setVolume(50); // Simplified }
virtual void volumeDown() { // Decrease volume tv->setVolume(30); // Simplified }};
// Step 4: Extend the abstraction (different remote types)class BasicRemote : public RemoteControl {private: int volumeLevel = 50;
public: BasicRemote(std::shared_ptr<TV> tv) : RemoteControl(tv) {}
void volumeUp() override { // Increase volume volumeLevel = std::min(100, volumeLevel + 10); tv->setVolume(volumeLevel); }
void volumeDown() override { // Decrease volume volumeLevel = std::max(0, volumeLevel - 10); tv->setVolume(volumeLevel); }};
class AdvancedRemote : public RemoteControl {private: int volumeLevel = 50; bool isMuted = false;
public: AdvancedRemote(std::shared_ptr<TV> tv) : RemoteControl(tv) {}
void volumeUp() override { // Increase volume if (isMuted) { isMuted = false; } volumeLevel = std::min(100, volumeLevel + 10); tv->setVolume(volumeLevel); }
void volumeDown() override { // Decrease volume volumeLevel = std::max(0, volumeLevel - 10); tv->setVolume(volumeLevel); }
void mute() { // Mute/unmute TV isMuted = !isMuted; if (isMuted) { tv->setVolume(0); std::cout << "TV: Muted" << std::endl; } else { tv->setVolume(volumeLevel); std::cout << "TV: Unmuted" << std::endl; } }};
// Usage - Clean and flexible!int main() { // Create TVs (implementations) auto samsungTV = std::make_shared<SamsungTV>(); auto sonyTV = std::make_shared<SonyTV>();
// Create remotes (abstractions) with different TVs BasicRemote basicSamsung(samsungTV); AdvancedRemote advancedSamsung(samsungTV); BasicRemote basicSony(sonyTV); AdvancedRemote advancedSony(sonyTV);
std::cout << "=== Basic Remote with Samsung TV ===" << std::endl; basicSamsung.powerOn(); basicSamsung.volumeUp(); basicSamsung.volumeDown(); basicSamsung.powerOff();
std::cout << "\n=== Advanced Remote with Sony TV ===" << std::endl; advancedSony.powerOn(); advancedSony.volumeUp(); advancedSony.mute(); advancedSony.powerOff();
std::cout << "\n✅ Bridge Pattern allows independent variation of remotes and TVs!" << std::endl;
return 0;}using System;
// Step 1: Define the implementation interface (TV)public interface ITV{ /** Implementation interface - how TVs work */ void TurnOn(); void TurnOff(); void SetVolume(int level);}
// Step 2: Implement concrete TV classespublic class SamsungTV : ITV{ /** Samsung TV implementation */ private int volume = 0; private bool isOn = false;
public void TurnOn() { isOn = true; Console.WriteLine("Samsung TV: Turning on..."); }
public void TurnOff() { isOn = false; Console.WriteLine("Samsung TV: Turning off..."); }
public void SetVolume(int level) { volume = Math.Max(0, Math.Min(100, level)); Console.WriteLine($"Samsung TV: Volume set to {volume}"); }}
public class SonyTV : ITV{ /** Sony TV implementation */ private int volume = 0; private bool isOn = false;
public void TurnOn() { isOn = true; Console.WriteLine("Sony TV: Turning on..."); }
public void TurnOff() { isOn = false; Console.WriteLine("Sony TV: Turning off..."); }
public void SetVolume(int level) { volume = Math.Max(0, Math.Min(100, level)); Console.WriteLine($"Sony TV: Volume set to {volume}"); }}
// Step 3: Define the abstraction (RemoteControl)public class RemoteControl{ /** Abstraction - what user interacts with */ protected ITV tv; // Bridge to implementation
public RemoteControl(ITV tv) { this.tv = tv; }
public virtual void PowerOn() { /** Turn on the TV */ tv.TurnOn(); }
public virtual void PowerOff() { /** Turn off the TV */ tv.TurnOff(); }
public virtual void VolumeUp() { /** Increase volume */ tv.SetVolume(50); // Simplified }
public virtual void VolumeDown() { /** Decrease volume */ tv.SetVolume(30); // Simplified }}
// Step 4: Extend the abstraction (different remote types)public class BasicRemote : RemoteControl{ /** Basic remote - simple functionality */ private int volumeLevel = 50;
public BasicRemote(ITV tv) : base(tv) {}
public override void VolumeUp() { /** Increase volume */ volumeLevel = Math.Min(100, volumeLevel + 10); tv.SetVolume(volumeLevel); }
public override void VolumeDown() { /** Decrease volume */ volumeLevel = Math.Max(0, volumeLevel - 10); tv.SetVolume(volumeLevel); }}
public class AdvancedRemote : RemoteControl{ /** Advanced remote - extended functionality */ private int volumeLevel = 50; private bool isMuted = false;
public AdvancedRemote(ITV tv) : base(tv) {}
public override void VolumeUp() { /** Increase volume */ if (isMuted) { isMuted = false; } volumeLevel = Math.Min(100, volumeLevel + 10); tv.SetVolume(volumeLevel); }
public override void VolumeDown() { /** Decrease volume */ volumeLevel = Math.Max(0, volumeLevel - 10); tv.SetVolume(volumeLevel); }
public void Mute() { /** Mute/unmute TV */ isMuted = !isMuted; if (isMuted) { tv.SetVolume(0); Console.WriteLine("TV: Muted"); } else { tv.SetVolume(volumeLevel); Console.WriteLine("TV: Unmuted"); } }}
// Usage - Clean and flexible!class Program{ static void Main() { // Create TVs (implementations) ITV samsungTV = new SamsungTV(); ITV sonyTV = new SonyTV();
// Create remotes (abstractions) with different TVs RemoteControl basicSamsung = new BasicRemote(samsungTV); RemoteControl advancedSamsung = new AdvancedRemote(samsungTV); RemoteControl basicSony = new BasicRemote(sonyTV); AdvancedRemote advancedSony = new AdvancedRemote(sonyTV);
Console.WriteLine("=== Basic Remote with Samsung TV ==="); basicSamsung.PowerOn(); basicSamsung.VolumeUp(); basicSamsung.VolumeDown(); basicSamsung.PowerOff();
Console.WriteLine("\n=== Advanced Remote with Sony TV ==="); advancedSony.PowerOn(); advancedSony.VolumeUp(); advancedSony.Mute(); advancedSony.PowerOff();
Console.WriteLine("\n✅ Bridge Pattern allows independent variation of remotes and TVs!"); }}package main
import ( "fmt" "math")
// Step 1: Define the implementation interface (TV)type TV interface { TurnOn() TurnOff() SetVolume(level int)}
// Step 2: Concrete TV implementationstype SamsungTV struct { volume int isOn bool}
func (t *SamsungTV) TurnOn() { t.isOn = true; fmt.Println("Samsung TV: Turning on...") }func (t *SamsungTV) TurnOff() { t.isOn = false; fmt.Println("Samsung TV: Turning off...") }func (t *SamsungTV) SetVolume(level int) { t.volume = int(math.Max(0, math.Min(100, float64(level)))) fmt.Printf("Samsung TV: Volume set to %d\n", t.volume)}
type SonyTV struct { volume int isOn bool}
func (t *SonyTV) TurnOn() { t.isOn = true; fmt.Println("Sony TV: Turning on...") }func (t *SonyTV) TurnOff() { t.isOn = false; fmt.Println("Sony TV: Turning off...") }func (t *SonyTV) SetVolume(level int) { t.volume = int(math.Max(0, math.Min(100, float64(level)))) fmt.Printf("Sony TV: Volume set to %d\n", t.volume)}
// Step 3: Abstraction (RemoteControl) holding a TV via compositiontype RemoteControl struct { tv TV // Bridge to implementation}
func NewRemoteControl(tv TV) *RemoteControl { return &RemoteControl{tv: tv} }
func (r *RemoteControl) PowerOn() { r.tv.TurnOn() }func (r *RemoteControl) PowerOff() { r.tv.TurnOff() }func (r *RemoteControl) VolumeUp() { r.tv.SetVolume(50) }func (r *RemoteControl) VolumeDown() { r.tv.SetVolume(30) }
// Step 4: Extended abstractiontype BasicRemote struct { RemoteControl volumeLevel int}
func NewBasicRemote(tv TV) *BasicRemote { return &BasicRemote{RemoteControl: RemoteControl{tv: tv}, volumeLevel: 50}}
func (r *BasicRemote) VolumeUp() { if r.volumeLevel < 100 { r.volumeLevel += 10 } r.tv.SetVolume(r.volumeLevel)}
func (r *BasicRemote) VolumeDown() { if r.volumeLevel > 0 { r.volumeLevel -= 10 } r.tv.SetVolume(r.volumeLevel)}
type AdvancedRemote struct { RemoteControl volumeLevel int isMuted bool}
func NewAdvancedRemote(tv TV) *AdvancedRemote { return &AdvancedRemote{RemoteControl: RemoteControl{tv: tv}, volumeLevel: 50}}
func (r *AdvancedRemote) VolumeUp() { r.isMuted = false if r.volumeLevel < 100 { r.volumeLevel += 10 } r.tv.SetVolume(r.volumeLevel)}
func (r *AdvancedRemote) Mute() { r.isMuted = !r.isMuted if r.isMuted { r.tv.SetVolume(0) fmt.Println("TV: Muted") } else { r.tv.SetVolume(r.volumeLevel) fmt.Println("TV: Unmuted") }}
func main() { samsung := &SamsungTV{} sony := &SonyTV{}
basicSamsung := NewBasicRemote(samsung) advancedSony := NewAdvancedRemote(sony)
fmt.Println("=== Basic Remote with Samsung TV ===") basicSamsung.PowerOn() basicSamsung.VolumeUp() basicSamsung.VolumeDown() basicSamsung.PowerOff()
fmt.Println("\n=== Advanced Remote with Sony TV ===") advancedSony.PowerOn() advancedSony.VolumeUp() advancedSony.Mute() advancedSony.PowerOff()
fmt.Println("\n✅ Bridge Pattern allows independent variation of remotes and TVs!")}// Class Structuretrait Renderer { fn render(&self, shape: &str);}trait Shape { fn draw(&self);}struct VectorRenderer;impl Renderer for VectorRenderer { fn render(&self, shape: &str) { println!("vector {}", shape); }}struct Circle<R: Renderer> { renderer: R,}impl<R: Renderer> Shape for Circle<R> { fn draw(&self) { self.renderer.render("circle"); }}Real-World Software Example: Shape Rendering System
Section titled “Real-World Software Example: Shape Rendering System”Now let’s see a realistic software example - a graphics system that needs to render different shapes using different rendering engines.
The Problem
Section titled “The Problem”You’re building a graphics application that needs to render shapes (Circle, Rectangle) using different rendering engines (Vector, Raster). Without Bridge Pattern:
# ❌ Without Bridge Pattern - Class explosion!
class VectorCircle: """Circle rendered using vector graphics""" def render(self): print("Rendering Circle using Vector graphics")
class RasterCircle: """Circle rendered using raster graphics""" def render(self): print("Rendering Circle using Raster graphics")
class VectorRectangle: """Rectangle rendered using vector graphics""" def render(self): print("Rendering Rectangle using Vector graphics")
class RasterRectangle: """Rectangle rendered using raster graphics""" def render(self): print("Rendering Rectangle using Raster graphics")
# Problems:# - Need 2 shapes × 2 renderers = 4 classes# - Adding new shape (Triangle) = 2 more classes# - Adding new renderer (SVG) = 3 more classes# - Exponential growth!// ❌ Without Bridge Pattern - Class explosion!
public class VectorCircle { // Circle rendered using vector graphics public void render() { System.out.println("Rendering Circle using Vector graphics"); }}
public class RasterCircle { // Circle rendered using raster graphics public void render() { System.out.println("Rendering Circle using Raster graphics"); }}
public class VectorRectangle { // Rectangle rendered using vector graphics public void render() { System.out.println("Rendering Rectangle using Vector graphics"); }}
public class RasterRectangle { // Rectangle rendered using raster graphics public void render() { System.out.println("Rendering Rectangle using Raster graphics"); }}
// Problems:// - Need 2 shapes × 2 renderers = 4 classes// - Adding new shape (Triangle) = 2 more classes// - Adding new renderer (SVG) = 3 more classes// - Exponential growth!// ❌ Without Bridge Pattern - Class explosion!
class VectorCircle { /** Circle rendered using vector graphics */ render(): void { console.log("Rendering Circle using Vector graphics"); }}
class RasterCircle { /** Circle rendered using raster graphics */ render(): void { console.log("Rendering Circle using Raster graphics"); }}
class VectorRectangle { /** Rectangle rendered using vector graphics */ render(): void { console.log("Rendering Rectangle using Vector graphics"); }}
class RasterRectangle { /** Rectangle rendered using raster graphics */ render(): void { console.log("Rendering Rectangle using Raster graphics"); }}
// Problems:// - Need 2 shapes × 2 renderers = 4 classes// - Adding new shape (Triangle) = 2 more classes// - Adding new renderer (SVG) = 3 more classes// - Exponential growth!// ❌ Without Bridge Pattern - Class explosion!
#include <iostream>
class VectorCircle {public: // Circle rendered using vector graphics void render() { std::cout << "Rendering Circle using Vector graphics" << std::endl; }};
class RasterCircle {public: // Circle rendered using raster graphics void render() { std::cout << "Rendering Circle using Raster graphics" << std::endl; }};
class VectorRectangle {public: // Rectangle rendered using vector graphics void render() { std::cout << "Rendering Rectangle using Vector graphics" << std::endl; }};
class RasterRectangle {public: // Rectangle rendered using raster graphics void render() { std::cout << "Rendering Rectangle using Raster graphics" << std::endl; }};
// Problems:// - Need 2 shapes × 2 renderers = 4 classes// - Adding new shape (Triangle) = 2 more classes// - Adding new renderer (SVG) = 3 more classes// - Exponential growth!// ❌ Without Bridge Pattern - Class explosion!
using System;
public class VectorCircle{ /** Circle rendered using vector graphics */ public void Render() { Console.WriteLine("Rendering Circle using Vector graphics"); }}
public class RasterCircle{ /** Circle rendered using raster graphics */ public void Render() { Console.WriteLine("Rendering Circle using Raster graphics"); }}
public class VectorRectangle{ /** Rectangle rendered using vector graphics */ public void Render() { Console.WriteLine("Rendering Rectangle using Vector graphics"); }}
public class RasterRectangle{ /** Rectangle rendered using raster graphics */ public void Render() { Console.WriteLine("Rendering Rectangle using Raster graphics"); }}
// Problems:// - Need 2 shapes × 2 renderers = 4 classes// - Adding new shape (Triangle) = 2 more classes// - Adding new renderer (SVG) = 3 more classes// - Exponential growth!// ❌ Without Bridge Pattern - Struct explosion!
package main
import "fmt"
type VectorCircle struct{}func (s *VectorCircle) Render() { fmt.Println("Rendering Circle using Vector graphics") }
type RasterCircle struct{}func (s *RasterCircle) Render() { fmt.Println("Rendering Circle using Raster graphics") }
type VectorRectangle struct{}func (s *VectorRectangle) Render() { fmt.Println("Rendering Rectangle using Vector graphics") }
type RasterRectangle struct{}func (s *RasterRectangle) Render() { fmt.Println("Rendering Rectangle using Raster graphics") }
// Problems:// - Need 2 shapes × 2 renderers = 4 structs// - Adding new shape (Triangle) = 2 more structs// - Adding new renderer (SVG) = 3 more structs// - Exponential growth!// The Problemstruct RedCircle;struct BlueCircle;struct RedSquare;struct BlueSquare;impl RedCircle { fn draw(&self) { println!("red circle"); }}impl BlueSquare { fn draw(&self) { println!("blue square"); }}Problems:
- Exponential class explosion - M shapes × N renderers = M×N classes
- Hard to extend - Adding new shape or renderer requires many classes
- Code duplication - Similar rendering logic repeated
The Solution: Bridge Pattern
Section titled “The Solution: Bridge Pattern”from abc import ABC, abstractmethod
# Step 1: Define the implementation interface (Renderer)class Renderer(ABC): """Implementation interface - how shapes are rendered"""
@abstractmethod def render_circle(self, radius: float) -> None: """Render a circle""" pass
@abstractmethod def render_rectangle(self, width: float, height: float) -> None: """Render a rectangle""" pass
# Step 2: Implement concrete renderer classesclass VectorRenderer(Renderer): """Vector graphics renderer"""
def render_circle(self, radius: float) -> None: print(f"Drawing Circle (radius: {radius}) using Vector graphics")
def render_rectangle(self, width: float, height: float) -> None: print(f"Drawing Rectangle ({width}x{height}) using Vector graphics")
class RasterRenderer(Renderer): """Raster graphics renderer"""
def render_circle(self, radius: float) -> None: print(f"Rasterizing Circle (radius: {radius}) using pixels")
def render_rectangle(self, width: float, height: float) -> None: print(f"Rasterizing Rectangle ({width}x{height}) using pixels")
# Step 3: Define the abstraction (Shape)class Shape: """Abstraction - what user interacts with"""
def __init__(self, renderer: Renderer): self.renderer = renderer # Bridge to implementation
def draw(self) -> None: """Draw the shape""" raise NotImplementedError
# Step 4: Extend the abstraction (different shapes)class Circle(Shape): """Circle shape"""
def __init__(self, renderer: Renderer, radius: float): super().__init__(renderer) self.radius = radius
def draw(self) -> None: """Draw the circle""" self.renderer.render_circle(self.radius)
class Rectangle(Shape): """Rectangle shape"""
def __init__(self, renderer: Renderer, width: float, height: float): super().__init__(renderer) self.width = width self.height = height
def draw(self) -> None: """Draw the rectangle""" self.renderer.render_rectangle(self.width, self.height)
# Usage - Clean and flexible!def main(): # Create renderers (implementations) vector_renderer = VectorRenderer() raster_renderer = RasterRenderer()
# Create shapes (abstractions) with different renderers vector_circle = Circle(vector_renderer, 5.0) raster_circle = Circle(raster_renderer, 5.0) vector_rect = Rectangle(vector_renderer, 10.0, 20.0) raster_rect = Rectangle(raster_renderer, 10.0, 20.0)
print("=== Vector Rendering ===") vector_circle.draw() vector_rect.draw()
print("\n=== Raster Rendering ===") raster_circle.draw() raster_rect.draw()
print("\n✅ Bridge Pattern allows independent variation of shapes and renderers!")
if __name__ == "__main__": main()// Step 1: Define the implementation interface (Renderer)interface Renderer { // Implementation interface - how shapes are rendered void renderCircle(double radius); void renderRectangle(double width, double height);}
// Step 2: Implement concrete renderer classesclass VectorRenderer implements Renderer { // Vector graphics renderer @Override public void renderCircle(double radius) { System.out.println("Drawing Circle (radius: " + radius + ") using Vector graphics"); }
@Override public void renderRectangle(double width, double height) { System.out.println("Drawing Rectangle (" + width + "x" + height + ") using Vector graphics"); }}
class RasterRenderer implements Renderer { // Raster graphics renderer @Override public void renderCircle(double radius) { System.out.println("Rasterizing Circle (radius: " + radius + ") using pixels"); }
@Override public void renderRectangle(double width, double height) { System.out.println("Rasterizing Rectangle (" + width + "x" + height + ") using pixels"); }}
// Step 3: Define the abstraction (Shape)abstract class Shape { // Abstraction - what user interacts with protected Renderer renderer; // Bridge to implementation
public Shape(Renderer renderer) { this.renderer = renderer; }
public abstract void draw();}
// Step 4: Extend the abstraction (different shapes)class Circle extends Shape { // Circle shape private double radius;
public Circle(Renderer renderer, double radius) { super(renderer); this.radius = radius; }
@Override public void draw() { // Draw the circle renderer.renderCircle(radius); }}
class Rectangle extends Shape { // Rectangle shape private double width; private double height;
public Rectangle(Renderer renderer, double width, double height) { super(renderer); this.width = width; this.height = height; }
@Override public void draw() { // Draw the rectangle renderer.renderRectangle(width, height); }}
// Usage - Clean and flexible!public class Main { public static void main(String[] args) { // Create renderers (implementations) Renderer vectorRenderer = new VectorRenderer(); Renderer rasterRenderer = new RasterRenderer();
// Create shapes (abstractions) with different renderers Shape vectorCircle = new Circle(vectorRenderer, 5.0); Shape rasterCircle = new Circle(rasterRenderer, 5.0); Shape vectorRect = new Rectangle(vectorRenderer, 10.0, 20.0); Shape rasterRect = new Rectangle(rasterRenderer, 10.0, 20.0);
System.out.println("=== Vector Rendering ==="); vectorCircle.draw(); vectorRect.draw();
System.out.println("\n=== Raster Rendering ==="); rasterCircle.draw(); rasterRect.draw();
System.out.println("\n✅ Bridge Pattern allows independent variation of shapes and renderers!"); }}// Step 1: Define the implementation interface (Renderer)interface Renderer { /** Implementation interface - how shapes are rendered */ renderCircle(radius: number): void; renderRectangle(width: number, height: number): void;}
// Step 2: Implement concrete renderer classesclass VectorRenderer implements Renderer { /** Vector graphics renderer */ renderCircle(radius: number): void { console.log(`Drawing Circle (radius: ${radius}) using Vector graphics`); }
renderRectangle(width: number, height: number): void { console.log(`Drawing Rectangle (${width}x${height}) using Vector graphics`); }}
class RasterRenderer implements Renderer { /** Raster graphics renderer */ renderCircle(radius: number): void { console.log(`Rasterizing Circle (radius: ${radius}) using pixels`); }
renderRectangle(width: number, height: number): void { console.log(`Rasterizing Rectangle (${width}x${height}) using pixels`); }}
// Step 3: Define the abstraction (Shape)abstract class Shape { /** Abstraction - what user interacts with */ protected renderer: Renderer; // Bridge to implementation
constructor(renderer: Renderer) { this.renderer = renderer; }
abstract draw(): void;}
// Step 4: Extend the abstraction (different shapes)class Circle extends Shape { /** Circle shape */ private radius: number;
constructor(renderer: Renderer, radius: number) { super(renderer); this.radius = radius; }
draw(): void { /** Draw the circle */ this.renderer.renderCircle(this.radius); }}
class Rectangle extends Shape { /** Rectangle shape */ private width: number; private height: number;
constructor(renderer: Renderer, width: number, height: number) { super(renderer); this.width = width; this.height = height; }
draw(): void { /** Draw the rectangle */ this.renderer.renderRectangle(this.width, this.height); }}
// Usage - Clean and flexible!function main(): void { // Create renderers (implementations) const vectorRenderer = new VectorRenderer(); const rasterRenderer = new RasterRenderer();
// Create shapes (abstractions) with different renderers const vectorCircle = new Circle(vectorRenderer, 5.0); const rasterCircle = new Circle(rasterRenderer, 5.0); const vectorRect = new Rectangle(vectorRenderer, 10.0, 20.0); const rasterRect = new Rectangle(rasterRenderer, 10.0, 20.0);
console.log("=== Vector Rendering ==="); vectorCircle.draw(); vectorRect.draw();
console.log("\n=== Raster Rendering ==="); rasterCircle.draw(); rasterRect.draw();
console.log("\n✅ Bridge Pattern allows independent variation of shapes and renderers!");}
main();#include <iostream>#include <memory>
// Step 1: Define the implementation interface (Renderer)class Renderer {public: virtual ~Renderer() = default; // Implementation interface - how shapes are rendered virtual void renderCircle(double radius) = 0; virtual void renderRectangle(double width, double height) = 0;};
// Step 2: Implement concrete renderer classesclass VectorRenderer : public Renderer {public: // Vector graphics renderer void renderCircle(double radius) override { std::cout << "Drawing Circle (radius: " << radius << ") using Vector graphics" << std::endl; }
void renderRectangle(double width, double height) override { std::cout << "Drawing Rectangle (" << width << "x" << height << ") using Vector graphics" << std::endl; }};
class RasterRenderer : public Renderer {public: // Raster graphics renderer void renderCircle(double radius) override { std::cout << "Rasterizing Circle (radius: " << radius << ") using pixels" << std::endl; }
void renderRectangle(double width, double height) override { std::cout << "Rasterizing Rectangle (" << width << "x" << height << ") using pixels" << std::endl; }};
// Step 3: Define the abstraction (Shape)class Shape {protected: std::shared_ptr<Renderer> renderer; // Bridge to implementation
public: Shape(std::shared_ptr<Renderer> renderer) : renderer(renderer) {} virtual ~Shape() = default; virtual void draw() = 0;};
// Step 4: Extend the abstraction (different shapes)class Circle : public Shape {private: double radius;
public: Circle(std::shared_ptr<Renderer> renderer, double radius) : Shape(renderer), radius(radius) {}
void draw() override { // Draw the circle renderer->renderCircle(radius); }};
class Rectangle : public Shape {private: double width; double height;
public: Rectangle(std::shared_ptr<Renderer> renderer, double width, double height) : Shape(renderer), width(width), height(height) {}
void draw() override { // Draw the rectangle renderer->renderRectangle(width, height); }};
// Usage - Clean and flexible!int main() { // Create renderers (implementations) auto vectorRenderer = std::make_shared<VectorRenderer>(); auto rasterRenderer = std::make_shared<RasterRenderer>();
// Create shapes (abstractions) with different renderers Circle vectorCircle(vectorRenderer, 5.0); Circle rasterCircle(rasterRenderer, 5.0); Rectangle vectorRect(vectorRenderer, 10.0, 20.0); Rectangle rasterRect(rasterRenderer, 10.0, 20.0);
std::cout << "=== Vector Rendering ===" << std::endl; vectorCircle.draw(); vectorRect.draw();
std::cout << "\n=== Raster Rendering ===" << std::endl; rasterCircle.draw(); rasterRect.draw();
std::cout << "\n✅ Bridge Pattern allows independent variation of shapes and renderers!" << std::endl;
return 0;}using System;
// Step 1: Define the implementation interface (Renderer)public interface IRenderer{ /** Implementation interface - how shapes are rendered */ void RenderCircle(double radius); void RenderRectangle(double width, double height);}
// Step 2: Implement concrete renderer classespublic class VectorRenderer : IRenderer{ /** Vector graphics renderer */ public void RenderCircle(double radius) { Console.WriteLine($"Drawing Circle (radius: {radius}) using Vector graphics"); }
public void RenderRectangle(double width, double height) { Console.WriteLine($"Drawing Rectangle ({width}x{height}) using Vector graphics"); }}
public class RasterRenderer : IRenderer{ /** Raster graphics renderer */ public void RenderCircle(double radius) { Console.WriteLine($"Rasterizing Circle (radius: {radius}) using pixels"); }
public void RenderRectangle(double width, double height) { Console.WriteLine($"Rasterizing Rectangle ({width}x{height}) using pixels"); }}
// Step 3: Define the abstraction (Shape)public abstract class Shape{ /** Abstraction - what user interacts with */ protected IRenderer renderer; // Bridge to implementation
protected Shape(IRenderer renderer) { this.renderer = renderer; }
public abstract void Draw();}
// Step 4: Extend the abstraction (different shapes)public class Circle : Shape{ /** Circle shape */ private double radius;
public Circle(IRenderer renderer, double radius) : base(renderer) { this.radius = radius; }
public override void Draw() { /** Draw the circle */ renderer.RenderCircle(radius); }}
public class Rectangle : Shape{ /** Rectangle shape */ private double width; private double height;
public Rectangle(IRenderer renderer, double width, double height) : base(renderer) { this.width = width; this.height = height; }
public override void Draw() { /** Draw the rectangle */ renderer.RenderRectangle(width, height); }}
// Usage - Clean and flexible!class Program{ static void Main() { // Create renderers (implementations) IRenderer vectorRenderer = new VectorRenderer(); IRenderer rasterRenderer = new RasterRenderer();
// Create shapes (abstractions) with different renderers Shape vectorCircle = new Circle(vectorRenderer, 5.0); Shape rasterCircle = new Circle(rasterRenderer, 5.0); Shape vectorRect = new Rectangle(vectorRenderer, 10.0, 20.0); Shape rasterRect = new Rectangle(rasterRenderer, 10.0, 20.0);
Console.WriteLine("=== Vector Rendering ==="); vectorCircle.Draw(); vectorRect.Draw();
Console.WriteLine("\n=== Raster Rendering ==="); rasterCircle.Draw(); rasterRect.Draw();
Console.WriteLine("\n✅ Bridge Pattern allows independent variation of shapes and renderers!"); }}package main
import "fmt"
// Step 1: Define the implementation interface (Renderer)type Renderer interface { RenderCircle(radius float64) RenderRectangle(width, height float64)}
// Step 2: Concrete renderer implementationstype VectorRenderer struct{}
func (r *VectorRenderer) RenderCircle(radius float64) { fmt.Printf("Drawing Circle (radius: %.1f) using Vector graphics\n", radius)}func (r *VectorRenderer) RenderRectangle(width, height float64) { fmt.Printf("Drawing Rectangle (%.1fx%.1f) using Vector graphics\n", width, height)}
type RasterRenderer struct{}
func (r *RasterRenderer) RenderCircle(radius float64) { fmt.Printf("Rasterizing Circle (radius: %.1f) using pixels\n", radius)}func (r *RasterRenderer) RenderRectangle(width, height float64) { fmt.Printf("Rasterizing Rectangle (%.1fx%.1f) using pixels\n", width, height)}
// Step 3: Abstraction (Shape) holding a Renderertype Shape interface { Draw()}
// Step 4: Concrete abstractionstype Circle struct { renderer Renderer radius float64}
func NewCircle(r Renderer, radius float64) *Circle { return &Circle{renderer: r, radius: radius} }func (c *Circle) Draw() { c.renderer.RenderCircle(c.radius) }
type Rectangle struct { renderer Renderer width, height float64}
func NewRectangle(r Renderer, w, h float64) *Rectangle { return &Rectangle{renderer: r, width: w, height: h}}func (r *Rectangle) Draw() { r.renderer.RenderRectangle(r.width, r.height) }
func main() { vector := &VectorRenderer{} raster := &RasterRenderer{}
vectorCircle := NewCircle(vector, 5.0) rasterCircle := NewCircle(raster, 5.0) vectorRect := NewRectangle(vector, 10.0, 20.0) rasterRect := NewRectangle(raster, 10.0, 20.0)
fmt.Println("=== Vector Rendering ===") vectorCircle.Draw() vectorRect.Draw()
fmt.Println("\n=== Raster Rendering ===") rasterCircle.Draw() rasterRect.Draw()
fmt.Println("\n✅ Bridge Pattern allows independent variation of shapes and renderers!")}// The Solution: Bridge Patterntrait Renderer { fn render(&self, shape: &str);}trait Shape { fn draw(&self);}struct VectorRenderer;impl Renderer for VectorRenderer { fn render(&self, shape: &str) { println!("vector {}", shape); }}struct Circle<R: Renderer> { renderer: R,}impl<R: Renderer> Shape for Circle<R> { fn draw(&self) { self.renderer.render("circle"); }}Bridge Pattern Variants
Section titled “Bridge Pattern Variants”The Bridge Pattern is typically implemented using composition (Object Bridge), but there are considerations:
1. Object Bridge (Composition) - Preferred
Section titled “1. Object Bridge (Composition) - Preferred”Uses composition - abstraction contains reference to implementation:
# Object Bridge - uses composition (preferred)class Implementation: def operation(self): return "Implementation"
class Abstraction: def __init__(self, impl: Implementation): self.impl = impl # Composition
def operation(self): return self.impl.operation()// Object Bridge - uses composition (preferred)interface Implementation { String operation();}
class Abstraction { private Implementation impl; // Composition
public Abstraction(Implementation impl) { this.impl = impl; }
public String operation() { return impl.operation(); }}// Object Bridge - uses composition (preferred)interface Implementation { operation(): string;}
class Abstraction { private impl: Implementation; // Composition
constructor(impl: Implementation) { this.impl = impl; }
operation(): string { return this.impl.operation(); }}#include <string>#include <memory>
// Object Bridge - uses composition (preferred)class Implementation {public: virtual ~Implementation() = default; virtual std::string operation() = 0;};
class Abstraction {private: std::shared_ptr<Implementation> impl; // Composition
public: Abstraction(std::shared_ptr<Implementation> impl) : impl(impl) {}
std::string operation() { return impl->operation(); }};// Object Bridge - uses composition (preferred)public interface IImplementation{ string Operation();}
public class Abstraction{ private IImplementation impl; // Composition
public Abstraction(IImplementation impl) { this.impl = impl; }
public string Operation() { return impl.Operation(); }}package main
// Object Bridge - uses composition (preferred, and only Go option)type Implementation interface { Operation() string}
type Abstraction struct { impl Implementation // Composition}
func NewAbstraction(impl Implementation) *Abstraction { return &Abstraction{impl: impl}}
func (a *Abstraction) Operation() string { return a.impl.Operation()}// 1. Object Bridge Composition - Preferredtrait Renderer { fn render(&self, shape: &str);}trait Shape { fn draw(&self);}struct VectorRenderer;impl Renderer for VectorRenderer { fn render(&self, shape: &str) { println!("vector {}", shape); }}struct Circle<R: Renderer> { renderer: R,}impl<R: Renderer> Shape for Circle<R> { fn draw(&self) { self.renderer.render("circle"); }}Pros: Flexible, can change implementation at runtime
Cons: Requires implementation instance
When to Use Bridge Pattern?
Section titled “When to Use Bridge Pattern?”Use Bridge Pattern when:
✅ You want to avoid permanent binding - Between abstraction and implementation
✅ Abstraction and implementation should vary independently - By subclassing
✅ Changes in implementation - Should not affect clients
✅ Hide implementation details - From clients
✅ Multiple implementations - Need to share the same abstraction
✅ Runtime binding - Want to switch implementations at runtime
When NOT to Use Bridge Pattern?
Section titled “When NOT to Use Bridge Pattern?”Don’t use Bridge Pattern when:
❌ Simple one-to-one relationship - If abstraction and implementation are tightly coupled
❌ Performance critical - Bridge adds indirection (usually negligible)
❌ Over-engineering - Don’t add complexity for simple cases
❌ Abstraction is stable - If abstraction rarely changes, direct inheritance might be simpler
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Confusing Bridge with Adapter
Section titled “Mistake 1: Confusing Bridge with Adapter”# ❌ Confusing Bridge with Adapter
# Bridge Pattern: Separates abstraction from implementation# - Used when you want to avoid permanent binding# - Abstraction and implementation vary independently# - Planned separation from the start
# Adapter Pattern: Makes incompatible interfaces compatible# - Used when you have incompatible interfaces# - Adapts existing code to work together# - Reactive solution to incompatibility
# Example: Bridgeclass Shape: def __init__(self, renderer: Renderer): # Planned separation self.renderer = renderer
# Example: Adapterclass Adapter: def __init__(self, incompatible_class): # Making incompatible work self.adaptee = incompatible_class// ❌ Confusing Bridge with Adapter
// Bridge Pattern: Separates abstraction from implementation// - Used when you want to avoid permanent binding// - Abstraction and implementation vary independently// - Planned separation from the start
// Adapter Pattern: Makes incompatible interfaces compatible// - Used when you have incompatible interfaces// - Adapts existing code to work together// - Reactive solution to incompatibility
// Example: Bridgeabstract class Shape { protected Renderer renderer; // Planned separation public Shape(Renderer renderer) { this.renderer = renderer; }}
// Example: Adapterclass Adapter { private IncompatibleClass adaptee; // Making incompatible work public Adapter(IncompatibleClass adaptee) { this.adaptee = adaptee; }}// ❌ Confusing Bridge with Adapter
// Bridge Pattern: Separates abstraction from implementation// - Used when you want to avoid permanent binding// - Abstraction and implementation vary independently// - Planned separation from the start
// Adapter Pattern: Makes incompatible interfaces compatible// - Used when you have incompatible interfaces// - Adapts existing code to work together// - Reactive solution to incompatibility
// Example: Bridgeabstract class Shape { protected renderer: Renderer; // Planned separation constructor(renderer: Renderer) { this.renderer = renderer; }}
// Example: Adapterclass Adapter { private adaptee: IncompatibleClass; // Making incompatible work constructor(adaptee: IncompatibleClass) { this.adaptee = adaptee; }}#include <memory>
// ❌ Confusing Bridge with Adapter
// Bridge Pattern: Separates abstraction from implementation// - Used when you want to avoid permanent binding// - Abstraction and implementation vary independently// - Planned separation from the start
// Adapter Pattern: Makes incompatible interfaces compatible// - Used when you have incompatible interfaces// - Adapts existing code to work together// - Reactive solution to incompatibility
// Example: Bridgeclass Shape {protected: std::shared_ptr<Renderer> renderer; // Planned separationpublic: Shape(std::shared_ptr<Renderer> renderer) : renderer(renderer) {} virtual ~Shape() = default;};
// Example: Adapterclass Adapter {private: IncompatibleClass adaptee; // Making incompatible workpublic: Adapter(const IncompatibleClass& adaptee) : adaptee(adaptee) {}};// ❌ Confusing Bridge with Adapter
// Bridge Pattern: Separates abstraction from implementation// - Used when you want to avoid permanent binding// - Abstraction and implementation vary independently// - Planned separation from the start
// Adapter Pattern: Makes incompatible interfaces compatible// - Used when you have incompatible interfaces// - Adapts existing code to work together// - Reactive solution to incompatibility
// Example: Bridgepublic abstract class Shape{ protected IRenderer renderer; // Planned separation
public Shape(IRenderer renderer) { this.renderer = renderer; }}
// Example: Adapterpublic class Adapter{ private IncompatibleClass adaptee; // Making incompatible work
public Adapter(IncompatibleClass adaptee) { this.adaptee = adaptee; }}package main
// Bridge Pattern: Separates abstraction from implementation// - Used when you want to avoid permanent binding// - Abstraction and implementation vary independently// - Planned separation from the start
// Adapter Pattern: Makes incompatible interfaces compatible// - Used when you have incompatible interfaces// - Adapts existing code to work together// - Reactive solution to incompatibility
// Example: Bridgetype Renderer interface{ Render() }
type Shape struct { renderer Renderer // Planned separation}
// Example: Adaptertype IncompatibleStruct struct{}
type AdapterStruct struct { adaptee *IncompatibleStruct // Making incompatible work}// Mistake 1: Confusing Bridge with Adapterstruct RedCircle;struct BlueCircle;struct RedSquare;struct BlueSquare;impl RedCircle { fn draw(&self) { println!("red circle"); }}impl BlueSquare { fn draw(&self) { println!("blue square"); }}Mistake 2: Using Inheritance Instead of Composition
Section titled “Mistake 2: Using Inheritance Instead of Composition”# ❌ Bad: Using inheritance instead of compositionclass VectorCircle(Circle, VectorRenderer): # Multiple inheritance - not Bridge! pass
# ✅ Good: Using composition (Bridge Pattern)class Circle: def __init__(self, renderer: Renderer): # Composition self.renderer = renderer// ❌ Bad: Using inheritance instead of composition// Java doesn't support multiple inheritance, but if it did:// class VectorCircle extends Circle, VectorRenderer { } // Not Bridge!
// ✅ Good: Using composition (Bridge Pattern)abstract class Shape { protected Renderer renderer; // Composition public Shape(Renderer renderer) { this.renderer = renderer; }}// ❌ Bad: Using inheritance instead of composition// TypeScript doesn't support multiple inheritance// class VectorCircle extends Circle, VectorRenderer { } // Not allowed!
// ✅ Good: Using composition (Bridge Pattern)abstract class Shape { protected renderer: Renderer; // Composition constructor(renderer: Renderer) { this.renderer = renderer; }}#include <memory>
// ❌ Bad: Using inheritance instead of composition// class VectorCircle : public Circle, public VectorRenderer { }; // Multiple inheritance - not Bridge!
// ✅ Good: Using composition (Bridge Pattern)class Shape {protected: std::shared_ptr<Renderer> renderer; // Composition
public: Shape(std::shared_ptr<Renderer> renderer) : renderer(renderer) {} virtual ~Shape() = default;};// ❌ Bad: Using inheritance instead of composition// C# doesn't support multiple inheritance of classes// class VectorCircle : Circle, VectorRenderer { } // Not allowed!
// ✅ Good: Using composition (Bridge Pattern)public abstract class Shape{ protected IRenderer renderer; // Composition
public Shape(IRenderer renderer) { this.renderer = renderer; }}package main
// ❌ Bad: In Go there is no multiple inheritance, but the anti-pattern// equivalent is trying to embed both structs and getting tangled behavior.
// ✅ Good: Use composition — the only and idiomatic Go approachtype RendererImpl interface{ Render() }
type ShapeImpl struct { renderer RendererImpl // Composition — this IS the Bridge}// Mistake 2: Using Inheritance Instead of Compositionstruct RedCircle;struct BlueCircle;struct RedSquare;struct BlueSquare;impl RedCircle { fn draw(&self) { println!("red circle"); }}impl BlueSquare { fn draw(&self) { println!("blue square"); }}Mistake 3: Not Decoupling Properly
Section titled “Mistake 3: Not Decoupling Properly”# ❌ Bad: Abstraction knows too much about implementationclass Circle: def __init__(self, renderer: Renderer): self.renderer = renderer
def draw(self): if isinstance(self.renderer, VectorRenderer): # Bad: Checking implementation type! self.renderer.render_circle_vector(self.radius) elif isinstance(self.renderer, RasterRenderer): # Bad: Implementation details leak into abstraction! self.renderer.render_circle_raster(self.radius)
# ✅ Good: Abstraction only uses interfaceclass Circle: def __init__(self, renderer: Renderer): self.renderer = renderer
def draw(self): # Good: Only uses interface, doesn't know implementation details self.renderer.render_circle(self.radius)// ❌ Bad: Abstraction knows too much about implementationclass Circle extends Shape { public void draw() { if (renderer instanceof VectorRenderer) { // Bad: Checking implementation type! ((VectorRenderer) renderer).renderCircleVector(radius); } else if (renderer instanceof RasterRenderer) { // Bad: Implementation details leak into abstraction! ((RasterRenderer) renderer).renderCircleRaster(radius); } }}
// ✅ Good: Abstraction only uses interfaceclass Circle extends Shape { @Override public void draw() { // Good: Only uses interface, doesn't know implementation details renderer.renderCircle(radius); }}// ❌ Bad: Abstraction knows too much about implementationclass BadCircle { private renderer: Renderer; private radius: number;
constructor(renderer: Renderer, radius: number) { this.renderer = renderer; this.radius = radius; }
draw(): void { if (this.renderer instanceof VectorRenderer) { // Bad: Checking implementation type! (this.renderer as VectorRenderer).renderCircleVector(this.radius); } else if (this.renderer instanceof RasterRenderer) { // Bad: Implementation details leak into abstraction! (this.renderer as RasterRenderer).renderCircleRaster(this.radius); } }}
// ✅ Good: Abstraction only uses interfaceclass GoodCircle { private renderer: Renderer; private radius: number;
constructor(renderer: Renderer, radius: number) { this.renderer = renderer; this.radius = radius; }
draw(): void { // Good: Only uses interface, doesn't know implementation details this.renderer.renderCircle(this.radius); }}#include <memory>
// ❌ Bad: Abstraction knows too much about implementationclass BadCircle : public Shape {private: double radius;
public: void draw() override { // Bad: Checking implementation type using dynamic_cast! if (auto* vec = dynamic_cast<VectorRenderer*>(renderer.get())) { vec->renderCircleVector(radius); } else if (auto* ras = dynamic_cast<RasterRenderer*>(renderer.get())) { // Bad: Implementation details leak into abstraction! ras->renderCircleRaster(radius); } }};
// ✅ Good: Abstraction only uses interfaceclass GoodCircle : public Shape {private: double radius;
public: void draw() override { // Good: Only uses interface, doesn't know implementation details renderer->renderCircle(radius); }};// ❌ Bad: Abstraction knows too much about implementationpublic class BadCircle : Shape{ private double radius;
public override void Draw() { if (renderer is VectorRenderer) { // Bad: Checking implementation type! ((VectorRenderer)renderer).RenderCircleVector(radius); } else if (renderer is RasterRenderer) { // Bad: Implementation details leak into abstraction! ((RasterRenderer)renderer).RenderCircleRaster(radius); } }}
// ✅ Good: Abstraction only uses interfacepublic class GoodCircle : Shape{ private double radius;
public override void Draw() { // Good: Only uses interface, doesn't know implementation details renderer.RenderCircle(radius); }}package main
import "fmt"
type CircleRenderer interface { RenderCircle(radius float64)}
// ❌ Bad: Type-asserting to know which implementation is usedtype BadCircle struct { renderer CircleRenderer radius float64}
func (c *BadCircle) Draw() { switch r := c.renderer.(type) { case *VectorCircleRenderer: r.RenderCircleVector(c.radius) // Bad: Knows internals! case *RasterCircleRenderer: r.RenderCircleRaster(c.radius) // Bad: Implementation leaks in! }}
// ✅ Good: Only uses the interfacetype GoodCircle struct { renderer CircleRenderer radius float64}
func (c *GoodCircle) Draw() { // Good: Only uses interface, doesn't know implementation details c.renderer.RenderCircle(c.radius)}
type VectorCircleRenderer struct{}func (r *VectorCircleRenderer) RenderCircle(radius float64) { fmt.Printf("Vector circle %.1f\n", radius)}func (r *VectorCircleRenderer) RenderCircleVector(radius float64) { r.RenderCircle(radius)}
type RasterCircleRenderer struct{}func (r *RasterCircleRenderer) RenderCircle(radius float64) { fmt.Printf("Raster circle %.1f\n", radius)}func (r *RasterCircleRenderer) RenderCircleRaster(radius float64) { r.RenderCircle(radius)}// Mistake 3: Not Decoupling Properlystruct RedCircle;struct BlueCircle;struct RedSquare;struct BlueSquare;impl RedCircle { fn draw(&self) { println!("red circle"); }}impl BlueSquare { fn draw(&self) { println!("blue square"); }}Benefits of Bridge Pattern
Section titled “Benefits of Bridge Pattern”- Decoupling - Abstraction and implementation are separated
- Independent Variation - Can vary abstraction and implementation independently
- No Class Explosion - M abstractions + N implementations = M + N classes (not M×N)
- Runtime Binding - Can switch implementations at runtime
- Open/Closed Principle - Open for extension, closed for modification
- Hide Implementation - Implementation details hidden from clients
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Bridge Pattern?
Section titled “What is Bridge Pattern?”Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can vary independently. It uses composition to bridge the abstraction and implementation.
Why Use It?
Section titled “Why Use It?”- ✅ Avoid class explosion - M abstractions × N implementations = M×N classes without Bridge
- ✅ Independent variation - Change abstraction without changing implementation
- ✅ Runtime binding - Switch implementations at runtime
- ✅ Decoupling - Separate what you use from how it works
- ✅ Open/Closed Principle - Open for extension, closed for modification
How It Works?
Section titled “How It Works?”- Define implementation interface - How things work
- Implement concrete classes - Different implementations
- Define abstraction - What user interacts with
- Bridge them - Abstraction contains reference to implementation
- Extend independently - Add new abstractions or implementations independently
Key Components
Section titled “Key Components”Abstraction → Implementation Interface → Concrete Implementation- Abstraction - What user interacts with (e.g., RemoteControl)
- Implementation Interface - How things work (e.g., TV interface)
- Concrete Implementation - Actual implementation (e.g., SamsungTV, SonyTV)
- Bridge - Composition link between abstraction and implementation
Simple Example
Section titled “Simple Example”class Implementation: def operation(self): pass
class Abstraction: def __init__(self, impl: Implementation): self.impl = impl # Bridge def operation(self): return self.impl.operation()interface Implementation { String operation(); }
class Abstraction { protected Implementation impl; // Bridge Abstraction(Implementation impl) { this.impl = impl; } String operation() { return impl.operation(); }}interface Implementation { operation(): string; }
class Abstraction { constructor(protected impl: Implementation) {} // Bridge operation(): string { return this.impl.operation(); }}class Implementation {public: virtual std::string operation() = 0;};
class Abstraction { Implementation* impl; // Bridgepublic: Abstraction(Implementation* i) : impl(i) {} std::string operation() { return impl->operation(); }};interface IImplementation { string Operation(); }
class Abstraction { protected IImplementation impl; // Bridge public Abstraction(IImplementation impl) => this.impl = impl; public string Operation() => impl.Operation();}type Implementation interface { Operation() string }
type Abstraction struct { impl Implementation // Bridge}func NewAbstraction(impl Implementation) *Abstraction { return &Abstraction{impl: impl} }func (a *Abstraction) Operation() string { return a.impl.Operation() }// Simple Exampletrait Renderer { fn render(&self, shape: &str);}trait Shape { fn draw(&self);}struct VectorRenderer;impl Renderer for VectorRenderer { fn render(&self, shape: &str) { println!("vector {}", shape); }}struct Circle<R: Renderer> { renderer: R,}impl<R: Renderer> Shape for Circle<R> { fn draw(&self) { self.renderer.render("circle"); }}When to Use?
Section titled “When to Use?”✅ Avoid permanent binding between abstraction and implementation
✅ Abstraction and implementation should vary independently
✅ Multiple implementations need to share same abstraction
✅ Want runtime binding
✅ Facing class explosion (M×N problem)
When NOT to Use?
Section titled “When NOT to Use?”❌ Simple one-to-one relationship
❌ Performance critical (adds indirection)
❌ Over-engineering simple cases
❌ Abstraction is stable
Key Takeaways
Section titled “Key Takeaways”- Bridge Pattern = Separates abstraction from implementation
- Abstraction = What user interacts with
- Implementation = How it actually works
- Bridge = Composition link between them
- Benefit = Independent variation, no class explosion
- Use Case = When you have M abstractions and N implementations
Common Pattern Structure
Section titled “Common Pattern Structure”class Implementation: def operation(self): pass
class Abstraction: def __init__(self, impl: Implementation): self.impl = impl def operation(self): return self.impl.operation()
# Usageimpl = ConcreteImplementation()abstraction = ConcreteAbstraction(impl)abstraction.operation()interface Implementation { String operation(); }
class Abstraction { protected Implementation impl; Abstraction(Implementation impl) { this.impl = impl; } String operation() { return impl.operation(); }}
// UsageImplementation impl = new ConcreteImplementation();Abstraction abstraction = new ConcreteAbstraction(impl);abstraction.operation();interface Implementation { operation(): string; }
class Abstraction { constructor(protected impl: Implementation) {} operation(): string { return this.impl.operation(); }}
// Usageconst impl = new ConcreteImplementation();const abstraction = new ConcreteAbstraction(impl);abstraction.operation();class Implementation {public: virtual std::string operation() = 0;};
class Abstraction { Implementation* impl;public: Abstraction(Implementation* i) : impl(i) {} std::string operation() { return impl->operation(); }};
// UsageImplementation* impl = new ConcreteImplementation();Abstraction* abstraction = new ConcreteAbstraction(impl);abstraction->operation();interface IImplementation { string Operation(); }
class Abstraction { protected IImplementation impl; public Abstraction(IImplementation impl) => this.impl = impl; public string Operation() => impl.Operation();}
// UsageIImplementation impl = new ConcreteImplementation();Abstraction abstraction = new ConcreteAbstraction(impl);abstraction.Operation();type Implementation interface { Operation() string }
type Abstraction struct { impl Implementation}func NewAbstraction(impl Implementation) *Abstraction { return &Abstraction{impl: impl} }func (a *Abstraction) Operation() string { return a.impl.Operation() }
// Usage// impl := NewConcreteImplementation()// abstraction := NewConcreteAbstraction(impl)// abstraction.Operation()// Common Pattern Structuretrait Renderer { fn render(&self, shape: &str);}trait Shape { fn draw(&self);}struct VectorRenderer;impl Renderer for VectorRenderer { fn render(&self, shape: &str) { println!("vector {}", shape); }}struct Circle<R: Renderer> { renderer: R,}impl<R: Renderer> Shape for Circle<R> { fn draw(&self) { self.renderer.render("circle"); }}Remember
Section titled “Remember”- Bridge Pattern separates abstraction from implementation
- It uses composition, not inheritance
- It prevents class explosion (M×N → M+N)
- Use it when you need independent variation
- It’s about decoupling, not just wrapping!
Interview Focus: Bridge Pattern
Section titled “Interview Focus: Bridge Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation so that the two can vary independently. It uses composition to bridge the abstraction and implementation, preventing class explosion.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of when to use it
- Indicates you can explain concepts clearly
2. When to Use Bridge Pattern
Section titled “2. When to Use Bridge Pattern”Must mention:
- ✅ Avoid class explosion - M abstractions × N implementations = M×N classes
- ✅ Independent variation - Want to change abstraction without changing implementation
- ✅ Runtime binding - Want to switch implementations at runtime
- ✅ Decoupling - Separate what you use from how it works
Example scenario to give:
“I’d use Bridge Pattern when building a graphics system with different shapes (Circle, Rectangle) and different rendering engines (Vector, Raster). Without Bridge, I’d need Circle×Vector, Circle×Raster, Rectangle×Vector, Rectangle×Raster = 4 classes. With Bridge, I need 2 shapes + 2 renderers = 4 classes, but they can vary independently.”
Must discuss:
- Bridge: Planned separation, abstraction and implementation vary independently
- Adapter: Reactive solution, makes incompatible interfaces compatible
- Key difference: Bridge is planned, Adapter is reactive
Example to give:
“Bridge Pattern is used when you plan to separate abstraction from implementation from the start. Adapter Pattern is used when you have existing incompatible interfaces that need to work together. Bridge is about design, Adapter is about compatibility.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- No class explosion - M + N instead of M×N classes
- Independent variation - Change abstraction or implementation independently
- Runtime binding - Switch implementations at runtime
- Decoupling - Abstraction and implementation are separate
- Open/Closed Principle - Open for extension, closed for modification
Trade-offs to acknowledge:
- Complexity - Adds a layer of indirection
- Performance - Small overhead (usually negligible)
- Over-engineering risk - Can be overkill for simple cases
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Bridge Pattern and Adapter Pattern?”
A:
“Bridge Pattern is a planned separation of abstraction from implementation, allowing them to vary independently. Adapter Pattern is a reactive solution that makes incompatible interfaces compatible. Bridge is about design and flexibility, Adapter is about compatibility and integration.”
Q: “When would you use Bridge vs direct inheritance?”
A:
“I use Bridge when I need independent variation of abstraction and implementation. If I have M types of remotes and N types of TVs, inheritance would require M×N classes. Bridge requires only M + N classes. I use direct inheritance when there’s a stable one-to-one relationship.”
Q: “How does Bridge Pattern relate to SOLID principles?”
A:
“Bridge Pattern primarily supports the Open/Closed Principle - you can extend functionality (add new abstractions or implementations) without modifying existing code. It also supports Single Responsibility Principle by separating abstraction concerns from implementation concerns. Additionally, it helps with Dependency Inversion Principle by having abstractions depend on implementation interfaces rather than concrete implementations.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Bridge Pattern clearly in one sentence
- Explain when to use it (with examples showing class explosion)
- Describe Bridge vs Adapter Pattern
- Implement Bridge Pattern from scratch
- Compare with other structural patterns (Adapter, Decorator)
- List benefits and trade-offs
- Identify common mistakes
- Give 2-3 real-world examples
- Connect to SOLID principles
- Discuss when NOT to use it
- Explain M×N class explosion problem
Remember: Bridge Pattern is about separating abstraction from implementation - allowing them to vary independently and preventing class explosion! 🌉