The Bridge Pattern is a structural design pattern that separates the abstraction from its implementation, allowing both to vary independently. This pattern introduces a bridge interface that decouples the abstraction layer from the implementation layer, enabling independent extension of both.
Core Roles of the Bridge Pattern
- Abstraction: Defines the abstract interface and contains a reference to the implementor interface.
- Refined Abstraction: Inherits from the abstraction class and extends its functionality.
- Implementor: Defines the interface for implementor classes, which the abstraction class calls.
- Concrete Implementor: Implements the implementor interface and provides specific functionality.
Example Implementation of the Bridge Pattern
Below is an example of the Bridge Pattern implemented in a “Graphics Rendering System”. The graphics (abstraction) can have different shapes, while the rendering methods (implementation) can utilize different technologies. The Bridge Pattern allows them to vary and combine independently:
#include <iostream>
#include <string>
// Implementor interface: Renderer (defines rendering methods)
class Renderer {
public:
virtual void renderCircle(float x, float y, float radius) = 0;
virtual void renderSquare(float x, float y, float side) = 0;
virtual ~Renderer() = default;
};
// Concrete Implementor: Software Renderer
class SoftwareRenderer : public Renderer {
public:
void renderCircle(float x, float y, float radius) override {
std::cout << "Software rendering circle - Center(" << x << ", " << y << "), Radius " << radius << std::endl;
}
void renderSquare(float x, float y, float side) override {
std::cout << "Software rendering square - Top-left corner(" << x << ", " << y << "), Side length " << side << std::endl;
}
};
// Concrete Implementor: Hardware Renderer
class HardwareRenderer : public Renderer {
public:
void renderCircle(float x, float y, float radius) override {
std::cout << "Hardware accelerated rendering circle - Center(" << x << ", " << y << "), Radius " << radius << std::endl;
}
void renderSquare(float x, float y, float side) override {
std::cout << "Hardware accelerated rendering square - Top-left corner(" << x << ", " << y << "), Side length " << side << std::endl;
}
};
// Abstraction: Shape
class Shape {
protected:
Renderer* renderer; // Bridge reference: points to the implementor interface
public:
Shape(Renderer* r) : renderer(r) {}
virtual void draw() = 0;
virtual void resize(float factor) = 0;
virtual ~Shape() = default;
};
// Refined Abstraction: Circle
class Circle : public Shape {
private:
float x, y, radius;
public:
Circle(Renderer* r, float x, float y, float radius)
: Shape(r), x(x), y(y), radius(radius) {}
void draw() override {
renderer->renderCircle(x, y, radius);
}
void resize(float factor) override {
radius *= factor;
}
};
// Refined Abstraction: Square
class Square : public Shape {
private:
float x, y, side;
public:
Square(Renderer* r, float x, float y, float side)
: Shape(r), x(x), y(y), side(side) {}
void draw() override {
renderer->renderSquare(x, y, side);
}
void resize(float factor) override {
side *= factor;
}
};
// Client usage
int main() {
// Create different renderers (implementation)
Renderer* software = new SoftwareRenderer();
Renderer* hardware = new HardwareRenderer();
// Create different shapes (abstraction) and bridge with renderers
Shape* circle = new Circle(software, 5, 5, 10);
Shape* square = new Square(hardware, 2, 3, 8);
// Draw shapes
std::cout << "=== Initial Drawing ===" << std::endl;
circle->draw();
square->draw();
// Resize and draw again
std::cout << "\n=== Drawing After Resizing ===" << std::endl;
circle->resize(1.5f);
square->resize(0.8f);
circle->draw();
square->draw();
// Change renderer (demonstrating the flexibility of the bridge)
std::cout << "\n=== Drawing After Changing Renderer ===" << std::endl;
delete circle;
circle = new Circle(hardware, 5, 5, 10);
circle->draw();
// Clean up resources
delete circle;
delete square;
delete hardware;
delete software;
return 0;
}
How the Bridge Pattern Works
- The abstraction layer (e.g., shapes) defines the interface for the objects and contains a reference to the implementation layer.
- The implementation layer (e.g., renderers) defines the interface for concrete implementations, decoupling it from the abstraction layer.
- The abstraction layer completes specific functionalities by calling the interfaces of the implementation layer without knowing the details of the implementation.
- Both the abstraction and implementation layers can be extended independently, allowing for easy addition of new abstraction or implementation classes.
Differences Between Bridge Pattern and Adapter Pattern
- Bridge Pattern: Considers the separation of abstraction and implementation from the design phase, allowing both to vary independently.
- Adapter Pattern: Used when existing interfaces are incompatible, connecting two incompatible interfaces.
Application Scenarios of the Bridge Pattern
- When a class has two or more independent dimensions that need to vary and extend.
- When it is necessary to avoid a permanent binding relationship between the abstraction and implementation.
- When functionality needs to be extended through composition rather than inheritance.
- When a system needs to run on multiple platforms and requires different implementations based on the platform.
Advantages and Disadvantages of the Bridge Pattern
Advantages:
- Separates abstraction from implementation, allowing both to vary and extend independently.
- Reduces the number of subclasses, avoiding class explosion issues.
- Increases the flexibility and scalability of the system.
- Follows the Open-Closed Principle, allowing new abstractions or implementations without modifying existing code.
Disadvantages:
- Increases the complexity of the system, requiring an understanding of the relationship between the abstraction and implementation layers.
- Requires a higher level of understanding from developers to correctly identify the two independent dimensions of variation in the system.
The Bridge Pattern is widely used in actual C++ development, for example, in GUI libraries where window controls (abstraction) are often separated from specific drawing systems (implementation) using the Bridge Pattern; in database access, database operation interfaces (abstraction) and the driver implementations for different databases (implementation) are also commonly designed using the Bridge Pattern.