Bridge Design Pattern in C# with Example

1. Definition

The Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation, allowing both to vary independently. The pattern involves breaking down a larger system into separate layers of abstraction and implementation.

2. Problem Statement

Imagine you have a set of classes representing various graphical shapes and another set for different rendering methods. If you want to combine each shape with each rendering method, the number of subclasses can grow exponentially, leading to a combinatorial explosion.

3. Solution

The Bridge pattern suggests that you split the monolithic class (or a set of closely related classes) into separate hierarchies – abstraction and implementation. Instead of the Shape class creating the rendering mechanism, it will receive the renderer as a parameter, and delegate the drawing to it.

4. Real-World Use Cases

1. Creating cross-platform UI controls.

2. Implementing device drivers that need to work with various hardware platforms.

3. Any scenario where a matrix of "classes of classes" might occur.

5. Implementation Steps

1. Split the monolithic class into distinct classes.

2. Create an interface for the implementation layer.

3. Make the high-level class abstraction reference the implementation interface.

4. Both the abstraction and its implementation should be created independently.

6. Implementation in C#

// Step 2: The Implementation Interface
public interface IRenderer
{
    string Render(string shapeName);
}

// Concrete Implementations
public class VectorRenderer : IRenderer
{
    public string Render(string shapeName)
    {
        return $"Drawing {shapeName} as lines and points.";
    }
}

public class RasterRenderer : IRenderer
{
    public string Render(string shapeName)
    {
        return $"Drawing {shapeName} as pixels.";
    }
}

// Step 3: The Abstraction
public abstract class Shape
{
    protected IRenderer renderer;

    public Shape(IRenderer renderer)
    {
        this.renderer = renderer;
    }

    public abstract string Draw();
}

// Concrete Abstraction
public class Circle : Shape
{
    public Circle(IRenderer renderer) : base(renderer) { }

    public override string Draw()
    {
        return renderer.Render("Circle");
    }
}

public class Program
{
    public static void Main()
    {
        IRenderer vector = new VectorRenderer();
        Shape circle1 = new Circle(vector);
        Console.WriteLine(circle1.Draw());

        IRenderer raster = new RasterRenderer();
        Shape circle2 = new Circle(raster);
        Console.WriteLine(circle2.Draw());
    }
}

Output:

Drawing Circle as lines and points.
Drawing Circle as pixels.

Explanation:

The Bridge pattern decouples the Shape (an abstraction) from its Renderer (an implementation). Now, adding a new shape or a new renderer doesn't require altering the other hierarchy.

In the provided code, a Circle is a shape, and it uses an instance of a Renderer to draw itself. We have two types of renderers: VectorRenderer and RasterRenderer. Each renderer can draw any shape passed to it. The main advantage here is scalability and separation of concerns.

7. When to use?

1. Use the Bridge pattern when you want to divide and organize a monolithic class that has several orthogonal dimensions of change.

2. When you need to extend the classes in each dimension independently.

3. When you have a matrix-like structure of classes and want to avoid class explosion.


Comments