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

14 KiB

Interface Segregation Principle (ISP)

"No client should be forced to depend on methods it does not use." — Robert C. Martin

Problem

A single "fat" interface bundles many methods together. Some implementers only need a subset, so they are forced to provide dummy or raise NotImplementedError stubs for methods that are irrelevant to them. This creates:

  • Misleading contracts — callers think all methods are functional when they are not
  • Fragile changes — adding a method to the fat interface forces every implementer to change
  • Tight coupling — clients depend on capabilities they never invoke
  • Testing burden — mocks must implement unused methods

Classic smell: An implementer contains methods that pass, return nil, or throw UnsupportedOperationException.

Solution

Break the fat interface into smaller, focused interfaces — each representing one cohesive capability. Implementers compose only the interfaces they actually need. Callers depend on only the narrow interface they require.

When to Use

  • You see implementers with stub/no-op methods
  • Different callers use different subsets of the same interface
  • Adding a method to an interface forces unrelated implementers to change
  • You are designing a public API or SDK that many parties will implement

When to Avoid

  • The interface is genuinely small (2-3 methods) and all implementers use every method
  • Splitting would create a swarm of single-method interfaces with no semantic meaning
  • The system is small and the coupling cost is negligible

Pseudocode

Before — fat interface forces irrelevant methods

INTERFACE Worker:
    METHOD work()
    METHOD eat()
    METHOD sleep()

CLASS HumanWorker IMPLEMENTS Worker:
    METHOD work():  ... does work ...
    METHOD eat():   ... eats lunch ...
    METHOD sleep(): ... sleeps at night ...

CLASS RobotWorker IMPLEMENTS Worker:
    METHOD work():  ... does work ...
    METHOD eat():   RAISE "Robots don't eat!"    // Forced stub
    METHOD sleep(): RAISE "Robots don't sleep!"  // Forced stub

After — segregated interfaces

INTERFACE Workable:
    METHOD work()

INTERFACE Eatable:
    METHOD eat()

INTERFACE Sleepable:
    METHOD sleep()

CLASS HumanWorker IMPLEMENTS Workable, Eatable, Sleepable:
    METHOD work():  ... does work ...
    METHOD eat():   ... eats lunch ...
    METHOD sleep(): ... sleeps at night ...

CLASS RobotWorker IMPLEMENTS Workable:
    METHOD work():  ... does work ...
    // No eat() or sleep() — not needed, not forced

Python

"""
Interface Segregation Principle — Python

BEFORE: A single Worker ABC forces Robot to implement eat() and sleep().
AFTER:  Segregated ABCs let each class implement only what it needs.
"""

from abc import ABC, abstractmethod


# ============================================================
# BEFORE: Violating ISP — fat interface
# ============================================================

class WorkerBefore(ABC):
    @abstractmethod
    def work(self) -> str: ...

    @abstractmethod
    def eat(self) -> str: ...

    @abstractmethod
    def sleep(self) -> str: ...


class HumanBefore(WorkerBefore):
    def __init__(self, name: str):
        self.name = name

    def work(self) -> str:
        return f"{self.name} is writing code"

    def eat(self) -> str:
        return f"{self.name} is eating lunch"

    def sleep(self) -> str:
        return f"{self.name} is sleeping"


class RobotBefore(WorkerBefore):
    def __init__(self, model: str):
        self.model = model

    def work(self) -> str:
        return f"{self.model} is assembling parts"

    def eat(self) -> str:
        # Forced to implement — makes no sense for a robot
        raise NotImplementedError("Robots don't eat!")

    def sleep(self) -> str:
        # Forced to implement — makes no sense for a robot
        raise NotImplementedError("Robots don't sleep!")


# ============================================================
# AFTER: Following ISP — segregated interfaces
# ============================================================

class Workable(ABC):
    @abstractmethod
    def work(self) -> str: ...


class Eatable(ABC):
    @abstractmethod
    def eat(self) -> str: ...


class Sleepable(ABC):
    @abstractmethod
    def sleep(self) -> str: ...


class Human(Workable, Eatable, Sleepable):
    def __init__(self, name: str):
        self.name = name

    def work(self) -> str:
        return f"{self.name} is writing code"

    def eat(self) -> str:
        return f"{self.name} is eating lunch"

    def sleep(self) -> str:
        return f"{self.name} is sleeping"


class Robot(Workable):
    """Only implements Workable — no forced stubs."""

    def __init__(self, model: str):
        self.model = model

    def work(self) -> str:
        return f"{self.model} is assembling parts"


# A supervisor only cares about Workable — not eating or sleeping
class Supervisor:
    def __init__(self, workers: list[Workable]):
        self.workers = workers

    def run_shift(self) -> None:
        for worker in self.workers:
            print(f"  {worker.work()}")


# A cafeteria only cares about Eatable
class Cafeteria:
    def __init__(self, eaters: list[Eatable]):
        self.eaters = eaters

    def serve_lunch(self) -> None:
        for eater in self.eaters:
            print(f"  {eater.eat()}")


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

def main():
    print("--- BEFORE (ISP violated) ---")
    human_old = HumanBefore("Alice")
    robot_old = RobotBefore("RX-78")

    print(f"  {human_old.work()}")
    print(f"  {human_old.eat()}")
    print(f"  {robot_old.work()}")
    try:
        robot_old.eat()
    except NotImplementedError as e:
        print(f"  ERROR: {e}")
    try:
        robot_old.sleep()
    except NotImplementedError as e:
        print(f"  ERROR: {e}")

    print("\n--- AFTER (ISP applied) ---")
    alice = Human("Alice")
    bob = Human("Bob")
    rx78 = Robot("RX-78")
    t800 = Robot("T-800")

    print("Supervisor runs the shift (Workable only):")
    supervisor = Supervisor([alice, bob, rx78, t800])
    supervisor.run_shift()

    print("\nCafeteria serves lunch (Eatable only):")
    cafeteria = Cafeteria([alice, bob])  # Robots NOT included — no stubs needed
    cafeteria.serve_lunch()


if __name__ == "__main__":
    main()

Go

// Interface Segregation Principle — Go
//
// BEFORE: One large Worker interface forces Robot to implement Eat() and Sleep().
// AFTER:  Small interfaces — Robot implements only what it needs.

package main

import "fmt"

// ============================================================
// BEFORE: Violating ISP — fat interface
// ============================================================

type WorkerBefore interface {
	Work() string
	Eat() string
	Sleep() string
}

type HumanBefore struct{ Name string }

func (h HumanBefore) Work() string  { return h.Name + " is writing code" }
func (h HumanBefore) Eat() string   { return h.Name + " is eating lunch" }
func (h HumanBefore) Sleep() string { return h.Name + " is sleeping" }

type RobotBefore struct{ Model string }

func (r RobotBefore) Work() string  { return r.Model + " is assembling parts" }
func (r RobotBefore) Eat() string   { return "ERROR: " + r.Model + " cannot eat!" }   // Forced stub
func (r RobotBefore) Sleep() string { return "ERROR: " + r.Model + " cannot sleep!" } // Forced stub

// ============================================================
// AFTER: Following ISP — small, focused interfaces
// ============================================================

type Workable interface {
	Work() string
}

type Eatable interface {
	Eat() string
}

type Sleepable interface {
	Sleep() string
}

type Human struct{ Name string }

func (h Human) Work() string  { return h.Name + " is writing code" }
func (h Human) Eat() string   { return h.Name + " is eating lunch" }
func (h Human) Sleep() string { return h.Name + " is sleeping" }

type Robot struct{ Model string }

// Robot only implements Workable — no forced Eat or Sleep.
func (r Robot) Work() string { return r.Model + " is assembling parts" }

// RunShift only needs Workable — doesn't care about eating or sleeping.
func RunShift(workers []Workable) {
	for _, w := range workers {
		fmt.Printf("  %s\n", w.Work())
	}
}

// ServeLunch only needs Eatable.
func ServeLunch(eaters []Eatable) {
	for _, e := range eaters {
		fmt.Printf("  %s\n", e.Eat())
	}
}

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

func main() {
	fmt.Println("--- BEFORE (ISP violated) ---")
	hOld := HumanBefore{Name: "Alice"}
	rOld := RobotBefore{Model: "RX-78"}
	fmt.Printf("  %s\n", hOld.Work())
	fmt.Printf("  %s\n", hOld.Eat())
	fmt.Printf("  %s\n", rOld.Work())
	fmt.Printf("  %s\n", rOld.Eat())   // Forced — meaningless
	fmt.Printf("  %s\n", rOld.Sleep()) // Forced — meaningless

	fmt.Println("\n--- AFTER (ISP applied) ---")
	alice := Human{Name: "Alice"}
	bob := Human{Name: "Bob"}
	rx78 := Robot{Model: "RX-78"}
	t800 := Robot{Model: "T-800"}

	fmt.Println("Supervisor runs the shift (Workable only):")
	RunShift([]Workable{alice, bob, rx78, t800})

	fmt.Println("\nCafeteria serves lunch (Eatable only):")
	ServeLunch([]Eatable{alice, bob}) // Robots not included — no stubs needed
}

JavaScript

// Interface Segregation Principle — JavaScript
//
// BEFORE: A single MultiFunctionDevice class forces SimplePrinter to implement scan/fax.
// AFTER:  Focused mixins — each device implements only the capabilities it has.

// ============================================================
// BEFORE: Violating ISP — fat "interface"
// ============================================================

class MultiFunctionDeviceBefore {
  print(doc) { throw new Error("Not implemented"); }
  scan(doc)  { throw new Error("Not implemented"); }
  fax(doc)   { throw new Error("Not implemented"); }
}

class AllInOnePrinterBefore extends MultiFunctionDeviceBefore {
  print(doc) { return `Printing: ${doc}`; }
  scan(doc)  { return `Scanning: ${doc}`; }
  fax(doc)   { return `Faxing: ${doc}`; }
}

class SimplePrinterBefore extends MultiFunctionDeviceBefore {
  print(doc) { return `Printing: ${doc}`; }
  // Forced to have scan and fax — they just throw errors
}

// ============================================================
// AFTER: Following ISP — focused capability classes
// ============================================================

class Printer {
  print(doc) { throw new Error("Not implemented"); }
}

class Scanner {
  scan(doc) { throw new Error("Not implemented"); }
}

class Faxer {
  fax(doc) { throw new Error("Not implemented"); }
}

// Helper: simple mixin function (JS doesn't have multiple inheritance)
function mixin(target, ...sources) {
  for (const source of sources) {
    const descriptors = Object.getOwnPropertyDescriptors(source.prototype);
    delete descriptors.constructor;
    Object.defineProperties(target.prototype, descriptors);
  }
}

// AllInOnePrinter has print + scan + fax
class AllInOnePrinter {
  print(doc) { return `[AllInOne] Printing: ${doc}`; }
  scan(doc)  { return `[AllInOne] Scanning: ${doc}`; }
  fax(doc)   { return `[AllInOne] Faxing: ${doc}`; }
}

// SimplePrinter has only print — no scan/fax stubs needed
class SimplePrinter {
  print(doc) { return `[Simple] Printing: ${doc}`; }
}

// ScannerOnly has only scan
class ScannerOnly {
  scan(doc) { return `[Scanner] Scanning: ${doc}`; }
}

// Functions depend on narrow capabilities, not a fat interface:

/** @param {{ print: (doc: string) => string }} printer */
function printJob(printer, doc) {
  console.log(`  ${printer.print(doc)}`);
}

/** @param {{ scan: (doc: string) => string }} scanner */
function scanJob(scanner, doc) {
  console.log(`  ${scanner.scan(doc)}`);
}

/** @param {{ fax: (doc: string) => string }} faxer */
function faxJob(faxer, doc) {
  console.log(`  ${faxer.fax(doc)}`);
}

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

function main() {
  console.log("--- BEFORE (ISP violated) ---");
  const allOld = new AllInOnePrinterBefore();
  const simpleOld = new SimplePrinterBefore();

  console.log(`  ${allOld.print("report.pdf")}`);
  console.log(`  ${allOld.scan("photo.jpg")}`);
  console.log(`  ${simpleOld.print("letter.pdf")}`);
  try {
    simpleOld.scan("photo.jpg");
  } catch (e) {
    console.log(`  ERROR: ${e.message} (SimplePrinter forced to have scan)`);
  }
  try {
    simpleOld.fax("contract.pdf");
  } catch (e) {
    console.log(`  ERROR: ${e.message} (SimplePrinter forced to have fax)`);
  }

  console.log("\n--- AFTER (ISP applied) ---");
  const allInOne = new AllInOnePrinter();
  const simple   = new SimplePrinter();
  const scanner  = new ScannerOnly();

  console.log("Print jobs (only need print capability):");
  printJob(allInOne, "report.pdf");
  printJob(simple, "letter.pdf");

  console.log("\nScan jobs (only need scan capability):");
  scanJob(allInOne, "photo.jpg");
  scanJob(scanner, "document.pdf");

  console.log("\nFax jobs (only need fax capability):");
  faxJob(allInOne, "contract.pdf");
  // simple and scanner are NOT passed here — they don't have fax, and that's fine
}

main();
  • Facade — after segregating interfaces, a facade can aggregate multiple small interfaces into a convenient higher-level API.
  • DIP — small, focused interfaces are easier to invert and inject; ISP and DIP work well together.
  • Adapter — when a client is forced to use a fat interface, an adapter can expose only the relevant subset.