In software architecture, as projects grow, there is often a need to combine multiple types of abstractions with multiple types of implementations. If you're not careful, you quickly end up with a class explosion – dozens of possible combinations between functionalities, each requiring its own derived class.
This is where the Bridge Pattern comes into play, a structural pattern that clearly separates abstraction from implementation, allowing them to evolve independently.
🧩 What is Bridge Pattern?
Bridge Pattern provides us a way to connect two different class hierarchies:
-
one for abstraction (usually high-level logic)
-
one for implementation (concrete details)
Instead of having a single large hierarchy, you separate them, and communication between them is done through a bridge, represented by an interface or an abstract class.
Thus you have:
Abstraction ---> Implementation
🧠 Why is Bridge Pattern useful?
-
✔ Avoids subclass explosion
-
✔ Allows adding new implementations without modifying the abstraction
-
✔ Provides flexibility in configuration and extension
-
✔ It is excellent when working with multiple platforms, drivers, services, or devices
📦 Practical example (C# / .NET)
Let's say we have a system that sends notifications. Notifications can be:
-
Email
-
SMS
And the notification levels can be:
-
Simple
-
Important
Without Bridge Pattern, we would have classes like:
-
SimpleEmailNotification
-
ImportantEmailNotification
-
SimpleSmsNotification
-
ImportantSmsNotification
…and it becomes hard to scale.
With Bridge Pattern: we separate what we send from how we send it.
The implementation (the bridge):
public interface INotificationSender
{
void Send(string message);
}
public class EmailSender : INotificationSender
{
public void Send(string message)
{
Console.WriteLine($"Email sent: {message}");
}
}
public class SmsSender : INotificationSender
{
public void Send(string message)
{
Console.WriteLine($"SMS sent: {message}");
}
}
The abstraction (logic level):
public abstract class Notification
{
protected readonly INotificationSender _sender;
protected Notification(INotificationSender sender)
{
_sender = sender;
}
public abstract void Notify(string message);
}
Versions of the abstraction:
public class SimpleNotification : Notification
{
public SimpleNotification(INotificationSender sender) : base(sender) { }
public override void Notify(string message)
{
_sender.Send("[Simple] " + message);
}
}
public class ImportantNotification : Notification
{
public ImportantNotification(INotificationSender sender) : base(sender) { }
public override void Notify(string message)
{
_sender.Send("[IMPORTANT] " + message);
}
}
Usage:
var emailSender = new EmailSender();
var smsSender = new SmsSender();
Notification n1 = new SimpleNotification(emailSender);
n1.Notify("Hello!");
Notification n2 = new ImportantNotification(smsSender);
n2.Notify("You have an important message!");
🏁 Conclusion
Bridge Pattern is excellent for situations where you need flexibility between something abstract and the way it is implemented. Instead of creating a single large class hierarchy, you separate them into two independent hierarchies, connected through an interface (bridge).
It is ideal for:
-
cross-platform systems
-
pluggable modules
-
high extensibility scenarios
-
complex enterprise architectures