When a class varies along two dimensions at once (shape and color, remote type and TV brand), inheritance makes you write a subclass for every combination. Bridge splits the two dimensions into separate hierarchies and connects them with a reference, so each side can grow on its own.
Intent#
Decouple an abstraction from its implementation so that the two can vary independently. (GoF)
Analogy: TV remotes. The abstraction is the remote (basic, advanced); the implementation is the TV (Sony, Samsung, LG). A remote holds a reference to a TV instead of inheriting from one, so any remote works with any TV. No SonyBasicRemote, SamsungAdvancedRemote, and so on.
The key idea: connect two hierarchies with composition, not inheritance.
The problem: class explosion#
With inheritance alone, every shape needs a subclass for every color:
# Two dimensions: shape (Circle, Square) and color (Red, Blue, Green)
class Circle: ...
class Square: ...
class RedCircle(Circle): ...
class BlueCircle(Circle): ...
class GreenCircle(Circle): ...
class RedSquare(Square): ...
class BlueSquare(Square): ...
class GreenSquare(Square): ...
# 2 shapes × 3 colors = 6 classes; 5 shapes × 10 colors = 50.
# A new shape needs a class per color; a new color needs a class per shape.
With Bridge:
from abc import ABC, abstractmethod
class Color(ABC): # implementation hierarchy
@abstractmethod
def fill(self) -> str: ...
class Red(Color):
def fill(self) -> str:
return "red"
class Blue(Color):
def fill(self) -> str:
return "blue"
class Shape(ABC): # abstraction hierarchy
def __init__(self, color: Color):
self.color = color # the bridge
@abstractmethod
def draw(self) -> None: ...
class Circle(Shape):
def draw(self) -> None:
print(f"Circle in {self.color.fill()}")
class Square(Shape):
def draw(self) -> None:
print(f"Square in {self.color.fill()}")
Circle(Red()).draw()
Square(Blue()).draw()
# 2 shapes + 2 colors = 4 classes. A new shape or color is one class.
Structure#
Bridge splits one class hierarchy into two that are joined by a reference.
| Participant | Role |
|---|---|
| Abstraction | High-level interface the client uses; holds a reference to an Implementor (Remote) |
| Refined abstraction | Variants of the abstraction (BasicRemote, AdvancedRemote) |
| Implementor | Interface for the low-level side (Device) |
| Concrete implementor | Platform- or vendor-specific code (SonyTV, SamsungTV) |
Example: remote + device#
Each remote holds a Device and only calls its primitive operations:
from abc import ABC, abstractmethod
# Implementor
class Device(ABC):
@abstractmethod
def turn_on(self) -> None: ...
@abstractmethod
def turn_off(self) -> None: ...
# Concrete implementors
class SonyTV(Device):
def turn_on(self) -> None:
print("Sony TV: ON")
def turn_off(self) -> None:
print("Sony TV: OFF")
class SamsungTV(Device):
def turn_on(self) -> None:
print("Samsung TV: ON")
def turn_off(self) -> None:
print("Samsung TV: OFF")
# Abstraction: holds the bridge reference
class Remote(ABC):
def __init__(self, device: Device):
self.device = device
@abstractmethod
def operate(self) -> None: ...
# Refined abstractions
class BasicRemote(Remote):
def operate(self) -> None:
self.device.turn_on()
self.device.turn_off()
class AdvancedRemote(Remote):
def operate(self) -> None:
self.device.turn_on()
print(" (advanced features)")
self.device.turn_off()
# Any remote with any device
BasicRemote(SonyTV()).operate()
AdvancedRemote(SamsungTV()).operate()
Output:
Sony TV: ON
Sony TV: OFF
Samsung TV: ON
(advanced features)
Samsung TV: OFF
Real-world examples#
Bridge shows up wherever an API and its backends evolve separately:
- Database APIs: your code talks to a generic connection and cursor; vendor drivers (MySQL, PostgreSQL, Oracle) supply the implementation.
- GUI toolkits: widgets are the abstraction; OS-specific backend classes are the implementation.
- Logging: a logger API plus pluggable file/console/network appenders.
- Business systems:
- notification type × channel (email/SMS/Slack)
- report × format (PDF/Excel/HTML)
- file operations × storage backend (local/cloud)
- shapes × renderer (vector/raster)
When to use it#
Bridge earns its extra interfaces when:
- Two dimensions vary independently and both will grow.
- Inheritance would multiply classes (N × M).
- You want to switch implementations at runtime or support several platforms.
- Several abstractions should share one implementation.
When not to use it#
Skip it when:
- Only one dimension varies. Use Strategy or plain polymorphism.
- The system is tiny (two or three classes).
- The abstraction and implementation always change together, or the abstraction needs intimate implementation details.
Pitfalls#
Watch for these traps:
- Designing it too early. Bridge pays off when you're sure of two dimensions; guessing adds interfaces nobody needs.
- Leaky implementor interfaces. Keep
Deviceto primitive operations so the abstraction side can combine them freely. - Indirection. One more hop for every call and one more thing to trace when debugging.
Bridge vs similar patterns#
The main difference from its neighbours is how many hierarchies vary:
| Pattern | How many hierarchies vary | When it's applied |
|---|---|---|
| Strategy | One (the algorithm family; the context is fixed) | Any time |
| Bridge | Two (abstraction and implementation both have subclasses) | Designed up front |
| Adapter | n/a: fixes one mismatched interface | After the fact |
In code, the difference with Strategy looks like this:
from __future__ import annotations
# Strategy: only the strategy varies
class Sorter:
def __init__(self, strategy: SortStrategy):
self.strategy = strategy
def sort(self) -> None:
self.strategy.sort()
# Bridge: Remote has subclasses AND Device has subclasses
class Remote:
def __init__(self, device: Device):
self.device = device
def operate(self) -> None:
self.device.turn_on()
- vs Abstract Factory: Abstract Factory creates families of objects; Bridge composes two hierarchies at runtime. They're often used together: a factory picks which implementor to plug in.
- vs State: State changes behavior as an object's state changes; Bridge separates what from how.
Key takeaways#
- Bridge = two hierarchies (abstraction and implementation) joined by a has-a reference.
- It turns N × M subclasses into N + M classes.
- "Prefer composition over inheritance" when two things vary orthogonally.
- One varying dimension → Strategy. Two → Bridge. Fixing an existing mismatch → Adapter.