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

11 KiB

Open/Closed Principle (OCP)

"Software entities should be open for extension but closed for modification." — Bertrand Meyer

Problem

Every time a new requirement appears (a new shape, a new payment method, a new report format), you must crack open an existing class and add another if/else or switch branch. This means:

  • Existing, tested code is at risk of breakage with every change
  • The class grows into an ever-expanding conditional monster
  • Multiple developers touching the same function causes merge conflicts
  • Unit tests must be re-run for all cases even when only one new case is added

Classic smell: A function with a chain of if type == "X" ... else if type == "Y" ... that grows every sprint.

Solution

Define an abstraction (interface, abstract class, protocol) that captures the varying behavior. Each new variant implements the abstraction. The code that uses the abstraction never changes — it simply calls the interface method. New behavior is added by writing a new class, not by editing existing ones.

When to Use

  • You have a function with a growing chain of type-checks or switch statements
  • New variants of behavior are frequently added
  • You need to support plugins or extensions
  • You want to add features without risking regression in stable code

When to Avoid

  • The set of variants is truly fixed and unlikely to grow (e.g., boolean logic)
  • Introducing an interface would add indirection for only one or two cases
  • You are in early prototyping and the abstraction boundary is unclear

Pseudocode

Before — modification required for every new shape

FUNCTION calculate_total_area(shapes):
    total = 0
    FOR shape IN shapes:
        IF shape.type == "circle":
            total += PI * shape.radius ^ 2
        ELSE IF shape.type == "rectangle":
            total += shape.width * shape.height
        // Must edit here to add triangle, pentagon, etc.
    RETURN total

After — extension without modification

INTERFACE Shape:
    METHOD area() -> number

CLASS Circle IMPLEMENTS Shape:
    radius
    METHOD area(): RETURN PI * radius ^ 2

CLASS Rectangle IMPLEMENTS Shape:
    width, height
    METHOD area(): RETURN width * height

// Adding Triangle requires NO changes to existing code:
CLASS Triangle IMPLEMENTS Shape:
    base, height
    METHOD area(): RETURN 0.5 * base * height

FUNCTION calculate_total_area(shapes: list of Shape):
    total = 0
    FOR shape IN shapes:
        total += shape.area()    // No type-checking needed
    RETURN total

Python

"""
Open/Closed Principle — Python

BEFORE: AreaCalculator uses if/elif to handle each shape type.
AFTER:  Each shape implements area(). New shapes need zero changes to the calculator.
"""

import math
from abc import ABC, abstractmethod


# ============================================================
# BEFORE: Violating OCP — must modify calculator for each shape
# ============================================================

class AreaCalculatorBefore:
    @staticmethod
    def calculate(shapes: list[dict]) -> float:
        total = 0.0
        for shape in shapes:
            if shape["type"] == "circle":
                total += math.pi * shape["radius"] ** 2
            elif shape["type"] == "rectangle":
                total += shape["width"] * shape["height"]
            # Every new shape means editing this function
        return total


# ============================================================
# AFTER: Following OCP — extend by adding classes, not editing
# ============================================================

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        ...


class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def area(self) -> float:
        return math.pi * self.radius ** 2

    def __repr__(self) -> str:
        return f"Circle(radius={self.radius})"


class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def area(self) -> float:
        return self.width * self.height

    def __repr__(self) -> str:
        return f"Rectangle({self.width}x{self.height})"


# NEW shape — no existing code modified
class Triangle(Shape):
    def __init__(self, base: float, height: float):
        self.base = base
        self.height = height

    def area(self) -> float:
        return 0.5 * self.base * self.height

    def __repr__(self) -> str:
        return f"Triangle(base={self.base}, height={self.height})"


class AreaCalculator:
    """Works with ANY Shape — never needs modification."""

    @staticmethod
    def total_area(shapes: list[Shape]) -> float:
        return sum(shape.area() for shape in shapes)

    @staticmethod
    def print_report(shapes: list[Shape]) -> None:
        for shape in shapes:
            print(f"  {shape!r:>40}  area = {shape.area():.2f}")
        print(f"  {'TOTAL':>40}  area = {AreaCalculator.total_area(shapes):.2f}")


# ============================================================
# Demo
# ============================================================

def main():
    print("--- BEFORE (OCP violated) ---")
    old_shapes = [
        {"type": "circle", "radius": 5},
        {"type": "rectangle", "width": 4, "height": 6},
    ]
    print(f"  Total area: {AreaCalculatorBefore.calculate(old_shapes):.2f}")

    print("\n--- AFTER (OCP applied) ---")
    shapes: list[Shape] = [
        Circle(5),
        Rectangle(4, 6),
        Triangle(3, 7),  # Added without touching Circle, Rectangle, or AreaCalculator
    ]
    AreaCalculator.print_report(shapes)


if __name__ == "__main__":
    main()

Go

// Open/Closed Principle — Go
//
// BEFORE: totalArea() uses a switch on a type tag.
// AFTER:  Shape interface — add new shapes without touching existing code.

package main

import (
	"fmt"
	"math"
)

// ============================================================
// BEFORE: Violating OCP
// ============================================================

type shapeData struct {
	kind   string
	radius float64
	width  float64
	height float64
	base   float64
}

func totalAreaBefore(shapes []shapeData) float64 {
	total := 0.0
	for _, s := range shapes {
		switch s.kind {
		case "circle":
			total += math.Pi * s.radius * s.radius
		case "rectangle":
			total += s.width * s.height
		// Must edit here for every new shape
		}
	}
	return total
}

// ============================================================
// AFTER: Following OCP
// ============================================================

// Shape is the abstraction all shapes implement.
type Shape interface {
	Area() float64
	String() string
}

// Circle implements Shape.
type Circle struct{ Radius float64 }

func (c Circle) Area() float64   { return math.Pi * c.Radius * c.Radius }
func (c Circle) String() string  { return fmt.Sprintf("Circle(radius=%.1f)", c.Radius) }

// Rectangle implements Shape.
type Rectangle struct{ Width, Height float64 }

func (r Rectangle) Area() float64   { return r.Width * r.Height }
func (r Rectangle) String() string  { return fmt.Sprintf("Rectangle(%.1fx%.1f)", r.Width, r.Height) }

// Triangle — added without modifying Circle, Rectangle, or TotalArea.
type Triangle struct{ Base, Height float64 }

func (t Triangle) Area() float64   { return 0.5 * t.Base * t.Height }
func (t Triangle) String() string  { return fmt.Sprintf("Triangle(base=%.1f, height=%.1f)", t.Base, t.Height) }

// TotalArea works with any Shape — never needs modification.
func TotalArea(shapes []Shape) float64 {
	total := 0.0
	for _, s := range shapes {
		total += s.Area()
	}
	return total
}

func PrintReport(shapes []Shape) {
	for _, s := range shapes {
		fmt.Printf("  %-40s area = %.2f\n", s, s.Area())
	}
	fmt.Printf("  %-40s area = %.2f\n", "TOTAL", TotalArea(shapes))
}

// ============================================================
// Demo
// ============================================================

func main() {
	fmt.Println("--- BEFORE (OCP violated) ---")
	oldShapes := []shapeData{
		{kind: "circle", radius: 5},
		{kind: "rectangle", width: 4, height: 6},
	}
	fmt.Printf("  Total area: %.2f\n", totalAreaBefore(oldShapes))

	fmt.Println("\n--- AFTER (OCP applied) ---")
	shapes := []Shape{
		Circle{Radius: 5},
		Rectangle{Width: 4, Height: 6},
		Triangle{Base: 3, Height: 7},
	}
	PrintReport(shapes)
}

JavaScript

// Open/Closed Principle — JavaScript
//
// BEFORE: discountCalculator uses if/else for each customer type.
// AFTER:  Each customer type implements its own discount. New types need no edits.

// ============================================================
// BEFORE: Violating OCP
// ============================================================

function calculateDiscountBefore(customerType, amount) {
  if (customerType === "regular") {
    return amount * 0.05;
  } else if (customerType === "premium") {
    return amount * 0.10;
  } else if (customerType === "vip") {
    return amount * 0.20;
  }
  // Must edit this function for every new customer type
  return 0;
}

// ============================================================
// AFTER: Following OCP
// ============================================================

class CustomerDiscount {
  /** @param {number} amount */
  discount(amount) {
    return 0;
  }

  label() {
    return "Unknown";
  }
}

class RegularDiscount extends CustomerDiscount {
  discount(amount) {
    return amount * 0.05;
  }
  label() {
    return "Regular (5%)";
  }
}

class PremiumDiscount extends CustomerDiscount {
  discount(amount) {
    return amount * 0.10;
  }
  label() {
    return "Premium (10%)";
  }
}

class VIPDiscount extends CustomerDiscount {
  discount(amount) {
    return amount * 0.20;
  }
  label() {
    return "VIP (20%)";
  }
}

// NEW type — no existing code modified
class EmployeeDiscount extends CustomerDiscount {
  discount(amount) {
    return amount * 0.30;
  }
  label() {
    return "Employee (30%)";
  }
}

/** Works with ANY CustomerDiscount — never needs modification. */
function printDiscountReport(strategies, amount) {
  for (const strategy of strategies) {
    const d = strategy.discount(amount);
    console.log(`  ${strategy.label().padEnd(20)} on $${amount.toFixed(2)} = -$${d.toFixed(2)}`);
  }
}

// ============================================================
// Demo
// ============================================================

function main() {
  const amount = 200.0;

  console.log("--- BEFORE (OCP violated) ---");
  for (const type of ["regular", "premium", "vip"]) {
    const d = calculateDiscountBefore(type, amount);
    console.log(`  ${type.padEnd(20)} on $${amount.toFixed(2)} = -$${d.toFixed(2)}`);
  }

  console.log("\n--- AFTER (OCP applied) ---");
  const strategies = [
    new RegularDiscount(),
    new PremiumDiscount(),
    new VIPDiscount(),
    new EmployeeDiscount(),  // Added without touching any existing class
  ];
  printDiscountReport(strategies, amount);
}

main();
  • Strategy — strategy is the most direct OCP enabler: new behaviours are new strategy classes, no modification required.
  • Decorator — decorator adds responsibilities to objects at runtime without modifying the decorated class.
  • Template Method — template method lets subclasses extend a fixed algorithm skeleton without modifying the skeleton.
  • Visitor — visitor adds operations to a stable type hierarchy without modifying the types themselves.