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 or and hardware like webcams or DSLR cameras. Without a bridge, you might end up with a combinatorial explosion of classes (e.g., 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 basics, specifically:
- Class inheritance and composition.
- The concept of interfaces (how one object interacts with another).
- Basic familiarity with and 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, allows us to be even more functional. Since a device just "gets data," we can replace the entire device hierarchy with a 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 (ABC). This allows us to implement shared logic, such as managing a list of multiple devices, directly in the parent class.
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.
- 25%· products
- 13%· software development
- 13%· software development
- 13%· software development
- 13%· products
- Other topics
- 25%

Let's Take The Bridge Pattern To The Next Level
WatchArjanCodes // 17:13
On this channel, I post videos about programming and software design to help you take your coding skills to the next level. I'm an entrepreneur and a university lecturer in computer science, with more than 20 years of experience in software development and design. If you're a software developer and you want to improve your development skills, and learn more about programming in general, make sure to subscribe for helpful videos. I post a video here every Friday. If you have any suggestion for a topic you'd like me to cover, just leave a comment on any of my videos and I'll take it under consideration. Thanks for watching!