Pythonic Bridge Pattern: Decoupling with Protocols and ABCs
Overview of the Bridge Pattern
The Bridge pattern is a structural design pattern that separates a large class or a set of closely related classes into two separate hierarchies: abstraction and implementation. This matters because it allows both hierarchies to develop independently. Imagine a streaming application where you have services like YouTubeWebcam, YouTubeDSLR, TwitchWebcam). The Bridge pattern fixes this by making the service hold a reference to the device, decoupling the two.
Prerequisites
To follow this guide, you should understand
- Class inheritance and composition.
- The concept of interfaces (how one object interacts with another).
- Basic familiarity with Type Hintingand decorators.
Key Libraries & Tools
abc: The Abstract Base Classes module for defining formal interfaces.typing: SpecificallyProtocolfor structural subtyping andCallablefor functional approaches.dataclasses: Used to reduce boilerplate when creating data-holding classes.mermaid: A text-based diagramming tool used for visualizing UML relationships.

Code Walkthrough: From Protocols to ABCs
Initially, we can define the relationship using a Protocol. This allows for duck-typing where the service doesn't care about the device's class hierarchy, only that it has a get_buffer_data method.
from typing import Protocol
class StreamingDevice(Protocol):
def get_buffer_data(self) -> str: ...
However, Callable type alias. This removes the need for extra classes entirely.
from typing import Callable
BufferData = str
Buffer = Callable[[], BufferData]
def get_webcam_data() -> BufferData:
return "Webcam data stream"
To add more power, we move back to an
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
@dataclass
class StreamingService(ABC):
devices: list[Buffer] = field(default_factory=list)
def add_device(self, device: Buffer):
self.devices.append(device)
@abstractmethod
def start_stream(self):
...
Syntax Notes
- First-Class Functions: We pass functions like
get_webcam_dataas arguments without calling them (omitting the parentheses). This treats logic as data. - Default Factory: In dataclasses, we use
field(default_factory=list)to avoid the "mutable default argument" trap.
Practical Examples
- Cross-Platform UI: Separating a UI component (Button) from the OS implementation (Windows vs. macOS).
- Database Drivers: A generic
Databaseabstraction using specificPostgreSQLorMongoDBimplementations.
Tips & Gotchas
Avoid using Protocol when you need to share implementation logic; that's where ABC shines. If your implementation hierarchy is just a single method, skip the class and use a function to keep your codebase lean.

Fancy watching it?
Watch the full video and context