bigonotes
CS ConceptsDesign Patterns

Bridge Pattern

Split two independently varying dimensions into separate hierarchies joined by composition, turning N × M subclasses into N + M.

4 min readLast updated Design Patterns

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.

Bridge: a Remote hierarchy and a Device hierarchy joined by a single has-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 Device to 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.

Found a mistake? Tell me on LinkedIn ↗

Further reading#