[← 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 { <> #IMessageSender _sender +Notify(message)* } class IMessageSender { <> +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)