Bridge Design Pattern in C++



Bridge Design Pattern is a structural design pattern which helps to separate the following two aspects of a software system −

  • Abstraction − Provides the interface for client code, hiding the implementation details.
  • Implementation − Provides the concrete implementation of the abstraction.

By separating the above aspects, we can make both the abstraction and implementation independently expandable. This means that we can add new abstractions and implementations without affecting existing code.

Bridge Design Pattern

Think of it like a Coffee shop. You love coffee but alone it would be plain. Sometimes you want Espresso, sometimes Latte, and sometimes you want to add Milk, Sugar, or Honey.

The coffee type (Espresso, Latte) is the abstraction, and the add-ons (Milk, Sugar, Honey) are the implementations. Instead of creating separate classes for every possible combination (like "EspressoWithMilk" or "LatteWithSugar"), the Bridge Pattern lets coffee delegate to add-ons through composition. This makes the system more flexible, that allows new coffee types or new add-ons to be added without changing existing code, and avoids the explosion of subclasses.

Abstraction

The Abtraction here is not same as in Abstract classes or Interfaces. It is a higher level of abstraction that separates the interface from the implementation.

We only see "what" we need to see, instead of "how" it is implemented. The Abstraction contains a reference to the Implementor.

Abstraction Simlified Interfaces

Implementor

The Implementor defines the interface for implementation classes. This interface doesn't have to be exactly match to Abstraction's interface; in fact, the two interfaces can be different. Typically, the Implementor interface provides only primitive operations, and Abstraction defines higher-level operations based on those primitives.

We don't care about "what" it does, we care about "how" it is done. The Implementor doesn't contain any reference to the Abstraction.

Implementor Concrete Implimentation

Implementation of Bridge Design Pattern in C++

Let's implement the Bridge Design Pattern in C++ using the coffee shop example mentioned earlier. We'll create an abstraction for Coffee and an implementor for AddOns.

Steps to Implement Bridge Design Pattern

Following image shows the steps to implement Bridge Design Pattern.

Steps to Implement Bridge Design Pattern

C++ Code for Bridge Design Pattern

In this example, we have an abstract class AddOn that defines the interface for add-ons like Milk, Sugar, and Honey. The Coffee class is the abstraction that contains a reference to an AddOn. The concrete classes Espresso and Latte are refined abstractions that implement the serve() method.

#include <iostream>
using namespace std;

// Implementor: AddOn
class AddOn {
   public:
      virtual void add() = 0;
};

// Concrete AddOn: Milk
class Milk : public AddOn {
   public:
      void add() override {
         cout << "with Milk";
      }
};

// Concrete AddOn: Sugar
class Sugar : public AddOn {
   public:
      void add() override {
         cout << "with Sugar";
      }
};

// Concrete AddOn: Honey
class Honey : public AddOn {
   public:
      void add() override {
         cout << "with Honey";
      }
};

// Abstraction: Coffee
class Coffee {
   protected:
      // Bridge
     AddOn* addon;
   public:
      Coffee(AddOn* a) : addon(a) {}
      virtual void serve() = 0;
};

// Refined Abstraction: Espresso
class Espresso : public Coffee {
   public:
      Espresso(AddOn* a) : Coffee(a) {}
      void serve() override {
         cout << "Espresso ";
         addon->add();
         cout << endl;
      }
};

// Refined Abstraction: Latte
class Latte : public Coffee {
   public:
      Latte(AddOn* a) : Coffee(a) {}
      void serve() override {
         cout << "Latte ";
         addon->add();
         cout << endl;
      }
};

// Client
int main() {
   AddOn* milk = new Milk();
   AddOn* sugar = new Sugar();

   // Espresso with Milk
   Coffee* espressoMilk = new Espresso(milk);

   // Latte with Sugar
   Coffee* latteSugar = new Latte(sugar);

   espressoMilk->serve();
   latteSugar->serve();

   delete milk;
   delete sugar;
   delete espressoMilk;
   delete latteSugar;
}

Following is the output of the above code −

Espresso with Milk
Latte with Sugar

In this implementation, we can easily add new types of coffee or new add-ons without modifying existing code. For example, we can create a new class Mocha that extends Coffee or a new class Caramel that extends AddOn, and the existing classes will remain unaffected.

When to Use Bridge Design Pattern

  • Change how things work without changing what they do − Use Bridge to swap or update implementations anytime.
  • Grow both sides easily − Add new features or new ways of doing things without breaking old code.
  • Keep code clean and easy to change − Bridge keeps things simple and ready for easy updates.
  • Handle lots of choices and combinations − Manage many types and options without confusion.
  • Avoid too many classes − Prevent a mess of classes for every mix—use composition instead.

Real-world Software Use-cases of Bridge Design Pattern

Following is a diagram that shows some real-world software use-cases of Bridge Design Pattern.

Real-world Software Use-cases of Bridge Design Pattern

Conclusion

In this chapter, we learned about the Bridge Design Pattern, which is a structural design pattern that separates an abstraction from its implementation. We discussed the components of the pattern, including Abstraction and Implementor, and saw how they work together and provide way to extend both independently and maintain flexibility(composition over inheritance). We also implemented the Bridge Design Pattern in C++ using a coffee shop example, demonstrating how to create abstractions for Coffee and implementors for AddOns. Finally, we explored when to use the Bridge Design Pattern and some real-world software use-cases.

Advertisements