What Is This Pattern?

The Bridge pattern seeks to decouple an abstraction from its implementation such that both can vary independently. Effectively, the Bridge maintains a reference to both abstraction and implemention but doesn't implement either, thereby allowing the details of both to remain in their separate classes.

In object-oriented programming, the concept of inheritance is crucial to developing objects. This binds the implementation to its abstraction, and often that's exactly what we want. However, there can be scenarios in which one class inheriting from another might not be the best solution, particularly when multiple inheritances can be used. Into this void steps the Bridge design pattern.

This pattern is especially useful for scenarios in which changes to the implementation of an object should have no bearing on how their clients use said implementations. Bridge also differs from Adapter in that Bridge is used when designing new systems while Adapter is used to adapt old systems to new ones.

The Rundown

The Participants

The Abstraction defines an interface and maintains a reference to an Implementor.

The RefinedAbstraction extends the interface defined by the Abstraction.

The Implementor defines the interface for the ConcreteImplementor objects. This interface does not need to correspond to the Abstraction's interface.

The ConcreteImplementor objects implement the Implementor interface.

A Delicious Example

In real life, my brother has Celiac disease, and this means that his body cannot properly process gluten. He cannot eat wheat, rye, barley, oats, or anything made from any of those ingredients; if he does, he's probably going to be unwillingly asleep for the next six hours or so and could cause permanent damage to his digestive system. Consequently it can be difficult for him to order a meal from restaurants, since often they don't provide the proper special-needs meal he needs (and even if they do, the environment in which the food is prepped is often not properly ventilated or sterilized, making cross-contamination likely). In his honor, let's model a system by which we can order various special-needs meals from many different restaurants.

If only all those restaurants offered truly gluten-free meals.

The idea goes like this: I should be able to pick a type of special meal and pick a restaurant, without needing to know exactly what either of those things are (e.g. a dairy-free meal from a diner or a gluten-free meal from a fancy restaurant).

In a traditional inheritance model, we might have the following classes:

But what if we also need to keep track of what kind of restaurant the order came from? This is orthogonal to what the meal is, but is still a part of the model. In this case, we might end up with a crazy inheritance tree:

We also need the Abstraction participant, which for this demo is an abstract class and will define a method for sending an order and keep a reference to the Implementor:

/// <summary>
/// Abstraction which represents the sent order and maintains a reference to the restaurant where the order is going.
/// </summary>
public abstract class SendOrder
{
//Reference to the Implementor
public IOrderingSystem _restaurant;
public abstract void Send();
}

Now we can start defining our RefinedAbstraction classes. For this demo, let's take those two kinds of special-needs meals from earlier (dairy-free and gluten-free) and implement RefinedAbstraction objects for them.

If we run the app, we find that we can send any order to any restaurant. Further, if any of the abstractions (the orders) change their definition, the implementors don't actually care; and vice-versa, if the implementors change their implementation, the abstractions don't need to change as well.

Here's a screenshot of the demo app in action:

Will I Ever Use This Pattern?

Probably. As mentioned above, this pattern is very useful when designing systems where multiple different kinds of inheritance are possible; Bridge allows you to implement these inheritances without tightly binding to their abstractions.

That said, this is one of those patterns where the complexity needed to implement it may well cancel out its benefits. Don't forget to think critically about your situation and determine if you really need a pattern before you start refactoring toward it!

Summary

The Bridge design pattern seeks to allow abstractions and implementation to vary independently. This becomes useful when dealing with situations where regular inheritance would make us end up with too many classes.

As always, I like to provide code with my tutorials, so the repository for this pattern is over on GitHub and contains all of the sample code used here.