Bridge Pattern
The Bridge design pattern is used to decouple an abstraction from its implementation, allowing them to vary independently.
It provides a bridge between the two, enabling changes in one without affecting the other.
Think of it as a way to separate the abstract part of an object from the concrete implementation, allowing both to evolve independently.
Table of Contents
Bridge Pattern C++ Example:
Let’s create a simple example of the Bridge pattern to represent different types of shapes and colors.
#include <iostream>
#include <string>
// Implementor: Interface for different colors
class Color {
public:
virtual std::string getColor() const = 0;
virtual ~Color() {}
};
// Concrete Implementor: Red color
class RedColor : public Color {
public:
std::string getColor() const override {
return "Red";
}
};
// Concrete Implementor: Blue color
class BlueColor : public Color {
public:
std::string getColor() const override {
return "Blue";
}
};
// Abstraction: Interface for different shapes
class Shape {
public:
Shape(Color* color) : color_(color) {}
virtual void draw() const = 0;
virtual ~Shape() {}
protected:
Color* color_;
};
// Refined Abstraction: Circle shape
class Circle : public Shape {
public:
Circle(Color* color) : Shape(color) {}
void draw() const override {
std::cout << "Drawing a " << color_->getColor() << " Circle." << std::endl;
}
};
// Refined Abstraction: Square shape
class Square : public Shape {
public:
Square(Color* color) : Shape(color) {}
void draw() const override {
std::cout << "Drawing a " << color_->getColor() << " Square." << std::endl;
}
};
int main() {
// Create different shapes with different colors
Color* red = new RedColor();
Color* blue = new BlueColor();
Shape* redCircle = new Circle(red);
Shape* blueSquare = new Square(blue);
// Draw the shapes
redCircle->draw(); // Output: Drawing a Red Circle.
blueSquare->draw(); // Output: Drawing a Blue Square.
// Clean up
delete redCircle;
delete blueSquare;
delete red;
delete blue;
return 0;
}
Bridge Pattern Explanation:
In this example, we have two hierarchies: one for colors (Color
) and another for shapes (Shape
).
The Color
class is the Implementor hierarchy, which represents different colors. It provides an interface for obtaining the color as a string.
The RedColor
and BlueColor
classes are Concrete Implementors that implement the Color
interface with specific color values.
The Shape
class is the Abstraction hierarchy, representing different shapes. It contains a reference to a Color
object and provides an interface for drawing the shape.
The Circle
and Square
classes are Refined Abstractions that extend the Shape
class. They implement the draw()
method with specific shapes and use the Color
object to get the color to draw the shape.
In the main()
function, we demonstrate how the Bridge pattern works. We create different shapes (Circle
and Square
) with different colors (RedColor
and BlueColor
). Each shape uses its specific color to draw itself.
Applications of the Bridge Design Pattern:
- GUI Toolkits: Separating the abstraction of UI components from their platform-specific implementations.
- Database Drivers: Decoupling the database interface from different database management systems.
- Audio/Video Streaming: Separating the player from various media codecs and protocols.
Pros of the Bridge Design Pattern:
- Decoupling: It decouples the abstraction from its implementation, allowing both to change independently.
- Flexibility: It provides the flexibility to extend both the abstraction and the implementation hierarchies.
- Clean Code: It results in cleaner and more maintainable code, as it avoids the “cartesian product” complexity.
Cons of the Bridge Design Pattern:
- Complexity: May introduce additional complexity, especially in simple scenarios.
- Increased Classes: It can lead to an increase in the number of classes in the codebase.
- Design Overhead: Requires careful design to decide between an interface and abstract class hierarchy.
In summary, the Bridge pattern is useful when you want to separate the abstraction from its implementation to achieve flexibility and decoupling. It allows changes in one part of the code to have minimal impact on the other part. However, it’s essential to consider the trade-offs and use the pattern when there is a genuine need for separating abstraction and implementation.