Add code examples, and documentation
This commit is contained in:
205
docs/bridge.md
Normal file
205
docs/bridge.md
Normal file
@@ -0,0 +1,205 @@
|
||||
[← Design patterns index](README.md)
|
||||
|
||||
# Bridge
|
||||
|
||||
**Category:** Structural | **Source:** `patterns/bridge/Bridge Example/`
|
||||
|
||||
## Problem
|
||||
|
||||
A `Notification` hierarchy (`Alert`, `Reminder`) needs a second, independent
|
||||
dimension — delivery channel (`Email`, `SMS`). Subclassing both at once yields
|
||||
`EmailAlertNotification`, `SmsReminderNotification`, … and the count grows
|
||||
geometrically: each new notification type needs one subclass _per existing
|
||||
channel_, and each new channel needs one _per existing notification type_.
|
||||
|
||||
## Solution
|
||||
|
||||
Stop expressing both dimensions with inheritance. Keep one as a class hierarchy
|
||||
(the **Abstraction**), extract the other into its own hierarchy (the
|
||||
**Implementation**), and join them by composition — the abstraction holds a
|
||||
reference to an implementor and delegates the real work to it. Each hierarchy
|
||||
now grows independently, and the implementation is swappable at runtime.
|
||||
|
||||
> The GoF words "Abstraction" and "Implementation" do **not** mean your
|
||||
> language's `abstract`/`interface` keywords. They mean "the control-facing
|
||||
> half" and "the platform-facing half" of a split. (_Dive Into Design Patterns_,
|
||||
> p.165)
|
||||
|
||||
## Participants (GoF names → this repo)
|
||||
|
||||
- **Abstraction** → `Notification` — defines the client-facing interface, holds
|
||||
an `IMessageSender`.
|
||||
- **RefinedAbstraction** → `AlertNotification`, `ReminderNotification` —
|
||||
variants of control logic.
|
||||
- **Implementor** → `IMessageSender` — primitive operations; need not mirror the
|
||||
Abstraction's interface.
|
||||
- **ConcreteImplementor** → `EmailSender`, `SmsSender` — the channel-specific
|
||||
code.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Notification {
|
||||
<<abstract>>
|
||||
#IMessageSender _sender
|
||||
+Notify(message)*
|
||||
}
|
||||
class IMessageSender {
|
||||
<<interface>>
|
||||
+SendMessage(message)
|
||||
}
|
||||
Notification o--> IMessageSender : THE BRIDGE - delegates to
|
||||
Notification <|-- AlertNotification
|
||||
Notification <|-- ReminderNotification
|
||||
IMessageSender <|.. EmailSender
|
||||
IMessageSender <|.. SmsSender
|
||||
```
|
||||
|
||||
The single `o-->` arrow _is_ the bridge. Left of it, control logic grows freely;
|
||||
right of it, platforms grow freely; neither side multiplies the other.
|
||||
|
||||
## Code
|
||||
|
||||
```csharp
|
||||
// IMessageSender.cs — Implementor
|
||||
public interface IMessageSender
|
||||
{
|
||||
void SendMessage(string message);
|
||||
}
|
||||
|
||||
// EmailSender.cs — ConcreteImplementor
|
||||
public class EmailSender : IMessageSender
|
||||
{
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
Console.WriteLine($"Email sent: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
// SmsSender.cs — ConcreteImplementor
|
||||
public class SmsSender : IMessageSender
|
||||
{
|
||||
public void SendMessage(string message)
|
||||
{
|
||||
Console.WriteLine($"SMS sent: {message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Notification.cs — Abstraction
|
||||
public abstract class Notification
|
||||
{
|
||||
protected readonly IMessageSender _sender;
|
||||
|
||||
protected Notification(IMessageSender sender)
|
||||
{
|
||||
_sender = sender; // the bridge is established here, at construction
|
||||
}
|
||||
|
||||
public abstract void Notify(string message);
|
||||
}
|
||||
|
||||
// AlertNotification.cs — RefinedAbstraction (ReminderNotification is identical but for its prefix)
|
||||
public class AlertNotification : Notification
|
||||
{
|
||||
public AlertNotification(IMessageSender sender) : base(sender) { }
|
||||
|
||||
public override void Notify(string message)
|
||||
{
|
||||
_sender.SendMessage($"ALERT: {message}"); // high-level op built from the primitive
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Program.cs — the client picks both halves independently, at runtime
|
||||
Notification n1 = new AlertNotification(new EmailSender());
|
||||
Notification n2 = new AlertNotification(new SmsSender());
|
||||
Notification n3 = new ReminderNotification(new EmailSender());
|
||||
Notification n4 = new ReminderNotification(new SmsSender());
|
||||
|
||||
n1.Notify("Server is down");
|
||||
n2.Notify("Server is down");
|
||||
n3.Notify("Meeting at 3pm");
|
||||
n4.Notify("Meeting at 3pm");
|
||||
```
|
||||
|
||||
The payoff: at no point is there an `EmailAlertNotification` class or an
|
||||
`SmsReminderNotification` class — the notification type and the delivery channel
|
||||
bind together at runtime, not compile time. 2 abstractions × 2 implementors = 4
|
||||
usable combinations from **4** classes, not 4 combination classes. At 5 × 5 it
|
||||
would be 10 classes instead of 25.
|
||||
|
||||
## Output (verified)
|
||||
|
||||
```
|
||||
Email sent: ALERT: Server is down
|
||||
SMS sent: ALERT: Server is down
|
||||
Email sent: Reminder: Meeting at 3pm
|
||||
SMS sent: Reminder: Meeting at 3pm
|
||||
```
|
||||
|
||||
## GoF pseudocode (remote controls × devices)
|
||||
|
||||
```
|
||||
class RemoteControl is
|
||||
protected field device: Device
|
||||
constructor RemoteControl(device: Device) is this.device = device
|
||||
method togglePower() is
|
||||
if (device.isEnabled()) then device.disable() else device.enable()
|
||||
method volumeUp() is
|
||||
device.setVolume(device.getVolume() + 10) // high-level op from primitives
|
||||
|
||||
class AdvancedRemoteControl extends RemoteControl is
|
||||
method mute() is device.setVolume(0)
|
||||
|
||||
interface Device is
|
||||
method isEnabled() method enable() method disable()
|
||||
method getVolume() method setVolume(percent)
|
||||
|
||||
class Tv implements Device is // ...
|
||||
class Radio implements Device is // ...
|
||||
|
||||
tv = new Tv()
|
||||
remote = new RemoteControl(tv)
|
||||
remote.togglePower()
|
||||
```
|
||||
|
||||
## Applicability
|
||||
|
||||
- You want to avoid permanently binding an abstraction to one implementation —
|
||||
for example, when the implementation must be selected or switched at run-time.
|
||||
- Both abstraction and implementation should be extensible by subclassing,
|
||||
independently of each other.
|
||||
- Changes to an implementation must not force clients to recompile.
|
||||
- You already have a proliferation of combination classes (GoF calls this smell
|
||||
"nested generalizations").
|
||||
- You want to share one implementor among several abstraction objects, invisibly
|
||||
to the client.
|
||||
|
||||
## Pros / Cons
|
||||
|
||||
| Pros | Cons |
|
||||
| -------------------------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| Platform-independent classes; implementor swappable at runtime | Can over-complicate a class that was already small and cohesive |
|
||||
| Client only ever sees the high-level abstraction | |
|
||||
| Open/Closed — add abstractions or implementors independently | |
|
||||
| Single Responsibility — control logic vs. platform detail live apart | |
|
||||
|
||||
## Seen in the wild (.NET)
|
||||
|
||||
- ADO.NET — `DbConnection`/`DbCommand` as abstraction, per-provider drivers as
|
||||
implementors.
|
||||
- `ILogger` as an abstraction over pluggable logging providers.
|
||||
- Any `HttpMessageHandler` swapped into `HttpClient`.
|
||||
|
||||
## Related patterns
|
||||
|
||||
- **Adapter** is retrofitted onto existing incompatible classes; Bridge is
|
||||
designed up front.
|
||||
- **Bridge / State / Strategy** share the "delegate to a composed object" shape
|
||||
but differ in intent — the name still communicates the problem being solved.
|
||||
- **Abstract Factory** pairs well when only certain abstraction/implementor
|
||||
combinations are legal.
|
||||
|
||||
---
|
||||
|
||||
[All patterns](README.md) · **Next →** [Decorator](decorator.md)
|
||||
Reference in New Issue
Block a user