The Bridge Design Pattern is a structural design pattern that separates the abstraction .......
Karwan Essmat
Software Solution Architect | Full-Stack .NET & Angular Developer | Enterprise Application Specialist | Mobile Developer
Bridge Design Pattern
The Bridge Design Pattern is a structural design pattern that separates the abstraction (high-level control logic) from the implementation (low-level operational details). This allows both to evolve independently and provides flexibility in switching between implementations without affecting the abstraction.
1. Concept
In the Bridge Pattern:
- Abstraction defines the high-level functionality.
- Refined Abstraction extends the abstraction to add more features or variations.
- Implementor defines the interface for the implementation.
- Concrete Implementor provides specific implementations for the Implementor.
Why Use the Bridge Pattern?
- Decoupling: It separates abstraction from implementation, allowing independent evolution.
- Flexibility: You can add new abstractions or implementations without modifying the existing code.
- Scalability: Reduces the complexity of managing multiple abstraction-implementation combinations.
2. Bridge Pattern Structure
The Bridge Pattern typically involves the following components:
- Abstraction:
- Refined Abstraction:
- Implementor:
- Concrete Implementors:
Example Scenario: Shape and Area Calculators
We will use a scenario where:
- The abstraction is a shape (IShape).
- The implementor is an area calculator (IAreaCalculator).
- Concrete implementations will calculate the area for different shapes (CircleAreaCalculator, RectangleAreaCalculator, SquareAreaCalculator).
3. Step-by-Step Implementation
Step 1: Define the Implementor Interface
The IAreaCalculator interface specifies a method for calculating the area.
// Implementor Interface
public interface IAreaCalculator
{
double CalculateArea(params double[] dimensions);
}
Explanation:
- The IAreaCalculator interface defines the contract for area calculation.
- It uses params double[] dimensions to handle varying numbers of parameters for different shapes (e.g., one for a circle, two for a rectangle).
Step 2: Create Concrete Implementations
These classes implement the IAreaCalculator interface and define the behavior for calculating the area of specific shapes.
Concrete Implementor: CircleAreaCalculator
// Concrete Implementor
public class CircleAreaCalculator : IAreaCalculator
{
public double CalculateArea(params double[] dimensions)
{
double radius = dimensions[0];
return Math.PI * radius * radius;
}
}
Concrete Implementor: RectangleAreaCalculator
// Concrete Implementor
public class RectangleAreaCalculator : IAreaCalculator
{
public double CalculateArea(params double[] dimensions)
{
double width = dimensions[0];
double height = dimensions[1];
return width * height;
}
}
Concrete Implementor: SquareAreaCalculator
// Concrete Implementor
public class SquareAreaCalculator : IAreaCalculator
{
public double CalculateArea(params double[] dimensions)
{
double side = dimensions[0];
return side * side;
}
}
Explanation:
- Each class provides a unique implementation of CalculateArea for its respective shape.
- The method signature is identical (as required by the IAreaCalculator interface).
Step 3: Define the Abstraction
The IShape abstraction defines a contract for shapes and uses an IAreaCalculator to calculate the area.
领英推è
// Abstraction
public interface IShape
{
double CalculateArea();
}
Explanation:
- The IShape abstraction provides a high-level interface for calculating the area.
- It does not directly handle the calculations—it delegates them to an IAreaCalculator.
Step 4: Create Refined Abstractions
These classes implement the IShape interface and use an IAreaCalculator to calculate the area for specific shapes.
Refined Abstraction: Circle
// Refined Abstraction
public class Circle(double radius, IAreaCalculator areaCalculator) : IShape
{
private readonly double _radius = radius;
private readonly IAreaCalculator _areaCalculator = areaCalculator;
public double CalculateArea()
{
return _areaCalculator.CalculateArea(_radius);
}
}
Refined Abstraction: Rectangle
// Refined Abstraction
public class Rectangle(double width, double height, IAreaCalculator areaCalculator) : IShape
{
private readonly double _width = width;
private readonly double _height = height;
private readonly IAreaCalculator _areaCalculator = areaCalculator;
public double CalculateArea()
{
return _areaCalculator.CalculateArea(_width, _height);
}
}
Refined Abstraction: Square
// Refined Abstraction
public class Square(double side, IAreaCalculator areaCalculator) : IShape
{
private readonly double _side = side;
private readonly IAreaCalculator _areaCalculator = areaCalculator;
public double CalculateArea()
{
return _areaCalculator.CalculateArea(_side);
}
}
Explanation:
- Each refined abstraction represents a specific shape and uses an IAreaCalculator to calculate its area.
- The calculation logic is completely decoupled from the shape's definition.
Step 5: Use the Bridge in the Main Program
The client code demonstrates how the abstraction (IShape) works with different implementations (IAreaCalculator).
public class Program
{
public static void Main()
{
// Using RectangleAreaCalculator with a Rectangle
IShape rectangle = new Rectangle(5, 10, new RectangleAreaCalculator());
Console.WriteLine($"Rectangle Area: {rectangle.CalculateArea()}");
// Using CircleAreaCalculator with a Circle
IShape circle = new Circle(7, new CircleAreaCalculator());
Console.WriteLine($"Circle Area: {circle.CalculateArea()}");
// Using SquareAreaCalculator with a Square
IShape square = new Square(4, new SquareAreaCalculator());
Console.WriteLine($"Square Area: {square.CalculateArea()}");
}
}
Output:
Rectangle Area: 50
Circle Area: 153.93
Square Area: 16
Explanation:
- The IShape abstraction interacts with different IAreaCalculator implementations.
- You can swap out one IAreaCalculator for another without modifying the shape classes.
4. Key Points
1. Shared Interface for Consistency:
· The IAreaCalculator ensures all implementations have the same method structure (CalculateArea).
· This allows the abstraction (IShape) to interact consistently with any calculator.
2. Behavioural Differences: the internal logic of each IAreaCalculator differs based on the geometry (circle, rectangle, square).
3. Decoupling: The abstraction (IShape) does not depend on specific implementations of IAreaCalculator.
4. Flexibility: You can add new shapes or calculators without modifying existing code, adhering to the Open/Closed Principle.
5. Advantages of the Bridge Pattern
- Decoupling: Separates abstraction from implementation.
- Scalability: Allows new abstractions and implementations to be added independently.
- Maintainability: Easier to manage and modify components.
Conclusion
The Bridge Design Pattern is a powerful technique for decoupling abstraction from implementation. By enforcing consistent structure through interfaces and allowing variations ibehaviouror, it enables flexible, scalable, and maintainable systems.
source code: https://github.com/karwanessmat/CSharpDesignPatterns/tree/master/2_Structural%20Design%20Patterns/Bridge/Bridge4Demo
#csharp #dotnet #programming #softwaredevelopment #designpattern #BridgePattern