Bridge Pattern
Separate two independently growing dimensions, such as notice kind and delivery channel, and connect them through composition.
· Last updated: 2026-10-25 · 7 min read
Overview
Normal and urgent notices can go through SMS or mail. A class for every combination gives four classes immediately, and another channel adds one class per notice kind.
A channel is a delivery route such as SMS or mail. Deciding whether to add an urgent prefix is a notice rule; delivering the prepared message is the channel’s job. Bridge separates these independently changing roles and connects a notice object to a channel object.
UML
Notice holds an IChannel. NormalNotice and UrgentNotice extend the abstraction side; SmsChannel and MailChannel extend delivery. The constructor connects the two.
History
GoF cataloged Bridge as a structural pattern in Design Patterns in 1994. It separates an abstraction from its implementation so each can vary independently.1
Why use it?
Changing the urgent wording should not require editing both SMS and mail implementations. Adding push delivery should not require rewriting the normal and urgent rules either. Notice uses only IChannel, the shared contract for passing a message to a channel, so the two responsibilities can change separately.
Adapter reconciles an existing incompatible contract. Bridge is a design separation between two dimensions. Both may wrap objects, so their purpose distinguishes them better than their shape.
Example
Connect a normal notice to SMS. The delivery classes return strings instead of contacting a messaging service.
using System;
using System.Collections.Generic;
using System.Linq;
Console.WriteLine(new NormalNotice(new SmsChannel()).Send("ready"));
// Notices pass messages through this contract without knowing an SMS or mail API.
interface IChannel { string Deliver(string message); }
// Return the selected channel name instead of sending an actual message.
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
// Keep the supplied channel. The constructor connects notice kind and delivery.
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
// The urgent prefix is a notice rule, so add it here instead of in every channel.
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
SMS: readyIn new NormalNotice(new SmsChannel()), the outer object selects the notice kind and the inner object selects its delivery route. NormalNotice passes the message to IChannel.Deliver() rather than calling an SMS API directly.
An extended example
Combine two notice kinds with two channels. The urgent prefix remains in UrgentNotice alone.
using System;
using System.Collections.Generic;
using System.Linq;
// Reuse the same notice kinds while replacing only the channel.
foreach (IChannel channel in new IChannel[] { new SmsChannel(), new MailChannel() })
{
Console.WriteLine(new NormalNotice(channel).Send("ready"));
Console.WriteLine(new UrgentNotice(channel).Send("ready"));
}
// Notices pass messages through this contract without knowing an SMS or mail API.
interface IChannel { string Deliver(string message); }
// Return the selected channel name instead of sending an actual message.
sealed class SmsChannel : IChannel
{
public string Deliver(string message) => "SMS: " + message;
}
sealed class MailChannel : IChannel
{
public string Deliver(string message) => "MAIL: " + message;
}
abstract class Notice(IChannel channel)
{
// Keep the supplied channel. The constructor connects notice kind and delivery.
protected IChannel Channel { get; } = channel;
public abstract string Send(string message);
}
sealed class NormalNotice(IChannel channel) : Notice(channel)
{
public override string Send(string message) => Channel.Deliver(message);
}
sealed class UrgentNotice(IChannel channel) : Notice(channel)
{
// The urgent prefix is a notice rule, so add it here instead of in every channel.
public override string Send(string message) => Channel.Deliver("URGENT " + message);
}
SMS: ready
SMS: URGENT ready
MAIL: ready
MAIL: URGENT readyFour combinations do not require four combination-specific classes. Real delivery also needs failure, cancellation and retry contracts; these stand-ins send no external messages.
TypeScript example
The TypeScript version separates the same roles. Send and Deliver in the UML correspond to send and deliver below; the constructor still connects a notice to its channel.
// Notices pass messages through this contract without knowing an SMS or mail API.
interface IChannel {
deliver(message: string): string;
}
// Return the selected channel name instead of sending an actual message.
class SmsChannel implements IChannel {
deliver(message: string): string { return "SMS: " + message; }
}
class MailChannel implements IChannel {
deliver(message: string): string { return "MAIL: " + message; }
}
abstract class Notice {
protected readonly channel: IChannel;
constructor(channel: IChannel) {
// Keep the supplied channel. The constructor connects notice kind and delivery.
this.channel = channel;
}
abstract send(message: string): string;
}
class NormalNotice extends Notice {
override send(message: string): string {
return this.channel.deliver(message);
}
}
class UrgentNotice extends Notice {
override send(message: string): string {
// The urgent prefix is a notice rule, so add it here instead of in every channel.
return this.channel.deliver("URGENT " + message);
}
}
// Reuse the same notice kinds while replacing only the channel.
for (const channel of [new SmsChannel(), new MailChannel()]) {
console.log(new NormalNotice(channel).send("ready"));
console.log(new UrgentNotice(channel).send("ready"));
}SMS: ready
SMS: URGENT ready
MAIL: ready
MAIL: URGENT readyA new channel that implements deliver() works with both NormalNotice and UrgentNotice. Adding a notice kind likewise leaves the existing channels unchanged.
Related reading
Each C# example is a separate .NET 10 console program. Compile the TypeScript example to JavaScript and run it with Node.js.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Structural patterns, Bridge. ↩