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:
sequenceDiagram
participant Client
participant BasicRemote as BasicRemote (Abstraction)
participant SamsungTV as SamsungTV (Implementation)
Client->>BasicRemote: power_on()
activate BasicRemote
BasicRemote->>SamsungTV: turn_on()
activate SamsungTV
SamsungTV-->>BasicRemote: TV turned on
deactivate SamsungTV
BasicRemote-->>Client: Success
deactivate BasicRemote
Note over Client,SamsungTV: Abstraction (Remote) uses<br/>Implementation (TV) through bridge!
Client->>BasicRemote: volume_up()
activate BasicRemote
BasicRemote->>SamsungTV: set_volume(current + 1)
activate SamsungTV
SamsungTV-->>BasicRemote: Volume increased
deactivate SamsungTV
BasicRemote-->>Client: Volume: 5
deactivate BasicRemote
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 PrincipleProblems:
- 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”classDiagram
class RemoteControl {
<<abstraction>>
-tv: TV
+power_on() void
+power_off() void
+volume_up() void
+volume_down() void
}
class BasicRemote {
+power_on() void
+power_off() void
+volume_up() void
+volume_down() void
}
class AdvancedRemote {
+power_on() void
+power_off() void
+volume_up() void
+volume_down() void
+mute() void
}
class TV {
<<interface>>
+turn_on() void
+turn_off() void
+set_volume(int) void
}
class SamsungTV {
+turn_on() void
+turn_off() void
+set_volume(int) void
}
class SonyTV {
+turn_on() void
+turn_off() void
+set_volume(int) void
}
RemoteControl <|-- BasicRemote : extends
RemoteControl <|-- AdvancedRemote : extends
RemoteControl --> TV : has (bridge)
TV <|.. SamsungTV : implements
TV <|.. SonyTV : implements
note for RemoteControl "Abstraction - what user interacts with"
note for TV "Implementation - how it actually works"
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!"); }}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!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!"); }}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(); }}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; }}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; }}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); }}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()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()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.”
3. Bridge vs Adapter Pattern
Section titled “3. Bridge vs Adapter Pattern”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! 🌉