d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
7.9 KiB
7.9 KiB
Bridge Pattern
Decouple an abstraction from its implementation so that the two can vary independently.
Problem
You have shapes (Circle, Square) and renderers (Vector, Raster). Without Bridge you'd need CircleVector, CircleRaster, SquareVector, SquareRaster — an N×M class explosion. Adding a new shape or renderer forces changes across all combinations.
Solution
Separate the two dimensions into independent hierarchies connected by composition. Shape holds a reference to a Renderer, delegating the rendering concern.
┌───────────────────┐ ┌──────────────────┐
│ Shape (abstract) │─────────▶│ Renderer (intf) │
│ + draw() │ │ + render_circle()│
│ + renderer │ │ + render_square()│
└──────┬────────────┘ └──────┬───────────┘
│ │
┌────┴────┐ ┌──────┴──────┐
│ Circle │ │VectorRender │
│ Square │ │RasterRender │
└─────────┘ └─────────────┘
When to Use
- You have two (or more) orthogonal dimensions of variation.
- You want to avoid a combinatorial explosion of subclasses.
- You need to switch implementations at runtime.
When to Avoid
- There is only one dimension of variation — simpler polymorphism suffices.
- The abstraction and implementation are unlikely to change independently.
- The added indirection makes the design harder to understand for a simple case.
Pseudocode
interface Renderer:
method render_circle(radius)
method render_square(side)
class VectorRenderer implements Renderer:
method render_circle(radius):
output "Drawing circle [vector] r=" + radius
method render_square(side):
output "Drawing square [vector] s=" + side
class RasterRenderer implements Renderer:
method render_circle(radius):
output "Drawing circle [raster] r=" + radius
method render_square(side):
output "Drawing square [raster] s=" + side
abstract class Shape:
field renderer: Renderer
constructor(renderer):
this.renderer = renderer
abstract method draw()
class Circle extends Shape:
field radius
constructor(renderer, radius):
super(renderer)
this.radius = radius
method draw():
renderer.render_circle(radius)
class Square extends Shape:
field side
constructor(renderer, side):
super(renderer)
this.side = side
method draw():
renderer.render_square(side)
// Usage
vector = new VectorRenderer()
raster = new RasterRenderer()
shapes = [Circle(vector, 5), Square(raster, 4), Circle(raster, 3)]
for s in shapes:
s.draw()
Python
from abc import ABC, abstractmethod
class Renderer(ABC):
"""Implementation interface."""
@abstractmethod
def render_circle(self, radius: float) -> None: ...
@abstractmethod
def render_square(self, side: float) -> None: ...
class VectorRenderer(Renderer):
def render_circle(self, radius: float) -> None:
print(f"Drawing circle [vector] r={radius}")
def render_square(self, side: float) -> None:
print(f"Drawing square [vector] s={side}")
class RasterRenderer(Renderer):
def render_circle(self, radius: float) -> None:
print(f"Drawing circle [raster] r={radius}")
def render_square(self, side: float) -> None:
print(f"Drawing square [raster] s={side}")
class Shape(ABC):
"""Abstraction that holds a reference to a Renderer."""
def __init__(self, renderer: Renderer) -> None:
self.renderer = renderer
@abstractmethod
def draw(self) -> None: ...
class Circle(Shape):
def __init__(self, renderer: Renderer, radius: float) -> None:
super().__init__(renderer)
self.radius = radius
def draw(self) -> None:
self.renderer.render_circle(self.radius)
class Square(Shape):
def __init__(self, renderer: Renderer, side: float) -> None:
super().__init__(renderer)
self.side = side
def draw(self) -> None:
self.renderer.render_square(self.side)
if __name__ == "__main__":
vector = VectorRenderer()
raster = RasterRenderer()
shapes: list[Shape] = [
Circle(vector, 5),
Square(raster, 4),
Circle(raster, 3),
Square(vector, 7),
]
for shape in shapes:
shape.draw()
Output:
Drawing circle [vector] r=5
Drawing square [raster] s=4
Drawing circle [raster] r=3
Drawing square [vector] s=7
Go
package main
import "fmt"
// --- Implementation interface ---
type Renderer interface {
RenderCircle(radius float64)
RenderSquare(side float64)
}
type VectorRenderer struct{}
func (v *VectorRenderer) RenderCircle(radius float64) {
fmt.Printf("Drawing circle [vector] r=%.0f\n", radius)
}
func (v *VectorRenderer) RenderSquare(side float64) {
fmt.Printf("Drawing square [vector] s=%.0f\n", side)
}
type RasterRenderer struct{}
func (r *RasterRenderer) RenderCircle(radius float64) {
fmt.Printf("Drawing circle [raster] r=%.0f\n", radius)
}
func (r *RasterRenderer) RenderSquare(side float64) {
fmt.Printf("Drawing square [raster] s=%.0f\n", side)
}
// --- Abstraction ---
type Shape interface {
Draw()
}
type Circle struct {
renderer Renderer
radius float64
}
func NewCircle(r Renderer, radius float64) *Circle {
return &Circle{renderer: r, radius: radius}
}
func (c *Circle) Draw() { c.renderer.RenderCircle(c.radius) }
type Square struct {
renderer Renderer
side float64
}
func NewSquare(r Renderer, side float64) *Square {
return &Square{renderer: r, side: side}
}
func (s *Square) Draw() { s.renderer.RenderSquare(s.side) }
func main() {
vector := &VectorRenderer{}
raster := &RasterRenderer{}
shapes := []Shape{
NewCircle(vector, 5),
NewSquare(raster, 4),
NewCircle(raster, 3),
NewSquare(vector, 7),
}
for _, s := range shapes {
s.Draw()
}
}
Output:
Drawing circle [vector] r=5
Drawing square [raster] s=4
Drawing circle [raster] r=3
Drawing square [vector] s=7
JavaScript
// --- Implementation ---
class VectorRenderer {
renderCircle(radius) {
console.log(`Drawing circle [vector] r=${radius}`);
}
renderSquare(side) {
console.log(`Drawing square [vector] s=${side}`);
}
}
class RasterRenderer {
renderCircle(radius) {
console.log(`Drawing circle [raster] r=${radius}`);
}
renderSquare(side) {
console.log(`Drawing square [raster] s=${side}`);
}
}
// --- Abstraction ---
class Circle {
constructor(renderer, radius) {
this.renderer = renderer;
this.radius = radius;
}
draw() {
this.renderer.renderCircle(this.radius);
}
}
class Square {
constructor(renderer, side) {
this.renderer = renderer;
this.side = side;
}
draw() {
this.renderer.renderSquare(this.side);
}
}
// --- Main ---
const vector = new VectorRenderer();
const raster = new RasterRenderer();
const shapes = [
new Circle(vector, 5),
new Square(raster, 4),
new Circle(raster, 3),
new Square(vector, 7),
];
shapes.forEach((s) => s.draw());
Output:
Drawing circle [vector] r=5
Drawing square [raster] s=4
Drawing circle [raster] r=3
Drawing square [vector] s=7
Related Patterns
- Adapter — adapter makes incompatible interfaces work together after the fact; bridge is designed upfront to let abstraction and implementation vary independently.
- Strategy — strategy changes the algorithm at runtime; bridge separates an abstraction from its implementation as a structural concern.
- Abstract Factory — abstract factory can create and configure a bridge, producing the right abstraction/implementation pair.