Files
freemo d9e5668cec fix(skills): comprehensive final audit pass for programming-patterns skill
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
2026-04-15 13:22:13 -04:00

7.9 KiB
Raw Permalink Blame History

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
  • 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.