Design Patterns (GoF and modern) Module 3 – Structural patterns: Adapter to Proxy
Bridge: split abstraction from implementation
Replace a class for every pair of message kind and delivery channel with two hierarchies joined by composition, and tell Bridge apart from look-alikes.
What you will learn
- Replace a class explosion of types times platforms with two hierarchies joined by composition
- Implement Bridge so the abstraction and its implementation vary independently at run time
- Explain how the C++ Pimpl idiom relates to Bridge and measure what it saves and costs
- Distinguish Bridge from Adapter and Strategy by intent
Before you start
What you’ll learn · Before you start
What you will learn
- Replace a class explosion of types times platforms with two hierarchies joined by composition
- Implement Bridge so the abstraction and its implementation vary independently at run time
- Explain how the C++ Pimpl idiom relates to Bridge and measure what it saves and costs
- Distinguish Bridge from Adapter and Strategy by intent
Before you start
On this page
Bridge splits a class hierarchy that grows in two directions into two hierarchies joined by composition. One side, the abstraction, is what clients use: here, the kinds of message an application sends, such as alerts, reminders and receipts. The other side, the implementation, is how the work gets done: delivery channels such as email, text messages and chat. Each message holds a channel object and calls it, so a new channel or a new kind of message is one new class, and the two sides vary independently, even while the program runs. Bridge is a design decision made up front, when you can see both directions of change coming; Adapter makes an existing interface fit after the fact.
Python 3.14.8, Node.js 24.21.0 and Apple clang 21.0.0 ran the programs below, and most of the Python and JavaScript ones can be run again in your browser.
The class explosion: three kinds times three channels
A natural first design gives each combination its own subclass: AlertEmail, AlertSms, AlertChat,
ReminderEmail and so on, nine classes for three kinds and three channels. Each class repeats the formatting of its
channel, so the rule for a text message lives in three places. The count grows as a product: a fourth channel adds
three classes, a fourth kind after it adds four, and five kinds over four channels need 20. In general, n kinds and m channels
need n × m subclasses.
Bridge turns the product into a sum. The kinds of message form one hierarchy and the channels another, and the base class of the kinds holds a reference to a channel. That reference is the bridge:
Two hierarchies joined by one reference
Text description of the diagram
The diagram shows two groups of boxes.
- The upper group is what is said, the abstraction. Alert, Reminder and Receipt, which supply a title and a body, extend Message, whose send(to) method sends them.
- The lower group is how it is delivered, the implementation. Email, Sms and Chat implement Channel, whose deliver(to, title, body) method delivers a message.
- An arrow from Message to Channel, labelled "holds one: the bridge", joins the two groups: each message holds a channel object and calls it.
New kinds of message are added to the upper group and new channels to the lower group, without touching the other.
Bridge in code
Message is the abstraction. Its send() asks its channel to deliver the message’s title and body, and each kind
supplies only those two parts. Channel is the implementation side, and each channel formats a delivery its own way:
"""Three kinds of message times three delivery channels, as a Bridge: 3 + 3 classes instead of 3 x 3 subclasses."""
from abc import ABC, abstractmethod
class Channel(ABC):
"""The implementation side: how a message reaches someone."""
@abstractmethod
def deliver(self, to: str, title: str, body: str) -> str: ...
class Email(Channel):
def deliver(self, to, title, body):
return f"email to {to}: subject {title!r}, {body}"
class Sms(Channel):
def deliver(self, to, title, body):
return f"sms to {to}: {title}: {body}"
class Chat(Channel):
def deliver(self, to, title, body):
return f"chat to {to}: *{title}* {body}"
class Message(ABC):
"""The abstraction side: what is said. It holds a channel and does not know which one."""
def __init__(self, channel: Channel) -> None:
self.channel = channel
def send(self, to: str) -> str:
return self.channel.deliver(to, self.title(), self.body())
@abstractmethod
def title(self) -> str: ...
@abstractmethod
def body(self) -> str: ...
class Alert(Message):
def __init__(self, channel: Channel, problem: str) -> None:
super().__init__(channel)
self.problem = problem
def title(self):
return "Alert"
def body(self):
return self.problem
class Reminder(Message):
def __init__(self, channel: Channel, event: str, at: str) -> None:
super().__init__(channel)
self.event, self.at = event, at
def title(self):
return "Reminder"
def body(self):
return f"{self.event} at {self.at}"
class Receipt(Message):
def __init__(self, channel: Channel, order: int) -> None:
super().__init__(channel)
self.order = order
def title(self):
return "Receipt"
def body(self):
return f"order {self.order} is paid"
for channel in (Email(), Sms(), Chat()):
for message in (Alert(channel, "disk 91% full"), Reminder(channel, "stand-up", "09:30"), Receipt(channel, 1042)):
print(message.send("asha"))
kinds, channels = len(Message.__subclasses__()), len(Channel.__subclasses__())
print(f"classes: {kinds} kinds + {channels} channels = {kinds + channels}; a subclass per pair would be {kinds * channels}")
reminder = Reminder(Email(), "stand-up", "09:30")
reminder.channel = Sms() # the implementation changes while the program runs
print(reminder.send("asha")) Output
email to asha: subject 'Alert', disk 91% full email to asha: subject 'Reminder', stand-up at 09:30 email to asha: subject 'Receipt', order 1042 is paid sms to asha: Alert: disk 91% full sms to asha: Reminder: stand-up at 09:30 sms to asha: Receipt: order 1042 is paid chat to asha: *Alert* disk 91% full chat to asha: *Reminder* stand-up at 09:30 chat to asha: *Receipt* order 1042 is paid classes: 3 kinds + 3 channels = 6; a subclass per pair would be 9 sms to asha: Reminder: stand-up at 09:30
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bridge.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The nine lines are every pairing of kind and channel, and they come from six classes, counted by the program from its own class hierarchy. The last line is the same reminder sent again after its channel was replaced, without a new object or a new class. That is the intent of the pattern: the abstraction and the implementation vary independently. With n kinds and m channels, a Bridge needs n + m classes instead of n × m, so five kinds over four channels need 9 classes instead of 20.
When a Bridge is worth it
Reach for a Bridge when you can name two independent directions of change before you write the classes, and both will grow: kinds of message and channels, shapes and renderers, reports and output formats, devices and their remote controls. If only one side will ever vary, a plain interface is enough. If the second side has one member and no second one is in sight, the extra layer is overhead you pay for nothing. The running cost of a Bridge is small: one more object per message and one more call per operation. The real work is designing the implementation’s interface, because every kind of message must be expressible through it.
Bridge and the patterns that look like it
Several patterns share the shape of an object that holds another object and delegates to it. What tells them apart is intent:
- Adapter makes an existing interface fit another one after both exist; a Bridge is planned before either side is written.
- Strategy swaps one algorithm inside an object, such as a sort order; a Bridge implementation is a whole platform or back end with several operations.
- Pimpl, a C++ idiom, is a Bridge with a single implementation: the header holds only a pointer, so the private members can change without recompiling the code that uses the class.
Check yourself
3 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Designing the implementation side: primitives, room and switching Pro: opens with a Pro, Premium or Ultimate pass
A Bridge is only as good as the interface between its two sides. This part designs that interface for channels that cannot all carry the same text, and moves messages to another channel while they are being sent.
In this part:
- Primitive operations, not one method per kind of message
- A channel that cannot carry every text: room measured in characters
- Switching the channel while messages are queued
2 runnable examples (JavaScript, Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Pimpl in C++: a Bridge with one implementation, measured on a build Pro: opens with a Pro, Premium or Ultimate pass
In C++ the Bridge has a second use that has nothing to do with several implementations: hiding a class’s private members from its header. This part builds the same program with and without Pimpl and counts what a change costs.
In this part:
- Why private members cost a rebuild
- Four files against one: the rebuild and the size, measured
- What Pimpl costs, and when it pays
3 runnable examples (C header, C++, Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Bridge, Adapter or Strategy: telling them apart by change requests Pro: opens with a Pro, Premium or Ultimate pass
Bridge, Adapter and Strategy all hold an object and delegate to it, so the code alone often cannot tell them apart. This part separates them by intent and by the changes each one makes cheap, with the classes counted.
In this part:
- The same shape, three intents
- Four change requests, with the classes counted
- Split shapes from the formats that draw them
- Results of the sample tests
1 runnable example (Python) · 1 exercise with sample tests · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Mid to senior
A notification module has twelve classes such as EmailAlert, SmsAlert and PushReceipt, and a new channel is planned. Walk through refactoring it into a Bridge. What goes in the channel interface, and how do you make sure every message still looks exactly as before?
-
Mid to senior
A colleague says your Bridge is just Strategy with extra steps. When are they right, and when is the difference real? Use a document exporter with several output formats as the example.
-
Senior to staff
You ship a C++ library as a shared library. A release added one private member to a public class, and applications that were not rebuilt started to crash. Explain why, and how the Pimpl idiom would have prevented it. What does Pimpl cost?
Key takeaways
- Bridge splits a hierarchy that grows in two directions into an abstraction and an implementation, joined by a reference from one to the other.
- n kinds and m implementations cost n + m classes with a Bridge, and n × m with a subclass for every pair.
- The abstraction holds its implementation in a field, so the implementation can be replaced while the program runs.
- Bridge is planned up front, Adapter fits existing interfaces together, and Strategy varies one algorithm.
References
- Design Patterns: Elements of Reusable Object-Oriented Software (Bridge) (Addison-Wesley (Pearson))
- C++ Core Guidelines, I.27: For stable library ABI, consider the Pimpl idiom (Standard C++ Foundation)
- ETSI TS 123 038 (3GPP TS 23.038): alphabets and language-specific information, SMS data coding (ETSI and 3GPP)
- GNU make manual: how make processes a makefile (Free Software Foundation)
- Python 3.14 documentation: abc (abstract base classes) (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress