Bridge Design Pattern in Swift

1. Definition

The Bridge design pattern is a structural pattern that decouples an abstraction from its implementation. This allows both to vary independently, promoting flexibility and scalability. Instead of binding an abstraction directly to its implementation, the bridge pattern uses composition to link them, ensuring a cleaner separation of concerns.

2. Problem Statement

Consider a scenario where you have multiple shapes, like circles and squares, and you want to render them in different ways (e.g., raster or vector). If you try to create subclasses for each combination of shape and rendering, the number of subclasses can grow exponentially, leading to a combinatorial explosion and making the codebase hard to maintain.

3. Solution

Instead of binding the shape and its rendering method together, the Bridge pattern suggests moving the rendering method into its hierarchy. The shape will then have a reference to the renderer, allowing you to combine any shape with any rendering method without creating subclass combinations.

4. Real-World Use Cases

1. UI toolkits where each widget can be drawn on multiple operating systems or using various libraries.

2. Connecting different databases with various drivers without changing application logic.

3. Providing multiple file format options for saving a document.

5. Implementation Steps

1. Create a separate interface for implementations.

2. Create concrete implementation classes.

3. Create an abstract base class representing the abstraction.

4. The base class maintains a reference to the implementation.

5. Extend the base class to create refined abstractions.

6. Implementation in Swift Programming

// Step 1: Define the implementation interface.
protocol Renderer {
    func renderShapeDescription(_ description: String)
}
// Step 2: Concrete implementations.
class VectorRenderer: Renderer {
    func renderShapeDescription(_ description: String) {
        print("Drawing \(description) as lines and arcs.")
    }
}
class RasterRenderer: Renderer {
    func renderShapeDescription(_ description: String) {
        print("Drawing \(description) as pixels.")
    }
}
// Step 3: Abstraction base class.
class Shape {
    var renderer: Renderer
    init(renderer: Renderer) {
        self.renderer = renderer
    }
    func draw() {
        fatalError("Subclasses need to implement this method.")
    }
}
// Step 4: Refined abstractions.
class Circle: Shape {
    override func draw() {
        renderer.renderShapeDescription("circle")
    }
}
class Square: Shape {
    override func draw() {
        renderer.renderShapeDescription("square")
    }
}
// Usage:
let raster = RasterRenderer()
let vector = VectorRenderer()
let circle = Circle(renderer: raster)
circle.draw()
let square = Square(renderer: vector)
square.draw()

Output:

Drawing circle as pixels.
Drawing square as lines and arcs.

Explanation:

1. We defined a Renderer protocol representing the implementation interface.

2. We then provided two concrete implementations, VectorRenderer and RasterRenderer.

3. The Shape class acts as our abstraction. It maintains a reference to a renderer.

4. Subclasses of Shape (e.g., Circle and Square) use the renderer to define how they should be drawn.

5. When drawing, the shape uses its associated renderer to determine how to render.

7. When to use?

Use the Bridge pattern when:

1. You want to avoid a permanent binding between an abstraction and its implementation.

2. Both the abstractions and their implementations should be extensible independently.

3. You want to hide implementation details from clients.


Comments