Bridge Design Pattern in Go

1. Definition

The Bridge Design Pattern decouples an abstraction from its implementation, allowing both to vary independently. This pattern involves an interface acting as a bridge that makes the functionality of concrete classes independent from interface implementer classes.

2. Problem Statement

You have a class hierarchy in both the abstraction and its implementation. When you want to combine different abstractions with different implementations, the class hierarchy explosion can occur, making the system hard to scale and manage.

3. Solution

Instead of a "hard-coded" binding between the abstraction and its implementation, introduce an intermediate interface that binds them together. This bridge between the abstraction and its implementation ensures they can vary independently.

4. Real-World Use Cases

1. Drawing shapes with different rendering engines.

2. Providing several user interface themes.

3. Connecting to various data sources with different protocols.

5. Implementation Steps

1. Identify the larger dimensions for the classes.

2. Split the dimensions into separate class hierarchies - Abstraction and Implementation.

3. Develop a bridge interface that can connect the Abstraction and Implementation.

4. Implement the concrete classes.

6. Implementation in Go

package main
import "fmt"
// Step 1: Implementation interface
type DrawingAPI interface {
	DrawCircle(radius float32, x float32, y float32)
}
// Concrete Implementers
type DrawingAPI1 struct{}
func (d *DrawingAPI1) DrawCircle(radius float32, x float32, y float32) {
	fmt.Printf("API1.circle at %f:%f radius %f\n", x, y, radius)
}
type DrawingAPI2 struct{}
func (d *DrawingAPI2) DrawCircle(radius float32, x float32, y float32) {
	fmt.Printf("API2.circle at %f:%f radius %f\n", x, y, radius)
}
// Step 2: Abstraction
type Shape interface {
	Draw()
	ResizeByPercentage(pct float32)
}
// Concrete shape
type CircleShape struct {
	x, y, radius float32
	drawingAPI   DrawingAPI
}
func (c *CircleShape) Draw() {
	c.drawingAPI.DrawCircle(c.radius, c.x, c.y)
}
func (c *CircleShape) ResizeByPercentage(pct float32) {
	c.radius *= pct
}
// Client code
func main() {
	shapes := []Shape{
		&CircleShape{1, 2, 3, &DrawingAPI1{}},
		&CircleShape{5, 7, 11, &DrawingAPI2{}},
	}
	for _, shape := range shapes {
		shape.ResizeByPercentage(2.5)
		shape.Draw()
	}
}

Output:

API1.circle at 1.000000:2.000000 radius 7.500000
API2.circle at 5.000000:7.000000 radius 27.500000

Explanation:

1. The DrawingAPI interface and its implementations (DrawingAPI1 and DrawingAPI2) represent the Implementor and ConcreteImplementor parts of the Bridge pattern.

2. The Shape interface (with the CircleShape concrete class) represents the Abstraction. The CircleShape contains an instance of DrawingAPI.

3. The client creates instances of CircleShape with different implementations of the DrawingAPI and uses them.

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 extendable independently.

3. Changes in the implementation of an abstraction should not impact clients.


Comments