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

Dependency Inversion Principle (DIP)

"High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions." — Robert C. Martin

Problem

A high-level module (business logic, orchestration) directly imports and instantiates a low-level module (database driver, email SDK, file system). This means:

  • Tight coupling — changing the database from PostgreSQL to MongoDB forces changes in business logic
  • Untestable — you cannot unit-test the business logic without a live database
  • Rigid — swapping implementations (e.g., email provider) requires editing the high-level code
  • Ripple effects — low-level implementation details leak into the architecture

Classic smell: Business logic classes contain import psycopg2, new MySQLConnection(), or require('nodemailer') directly.

Solution

Introduce an abstraction (interface, abstract class, protocol) that the high-level module depends on. The low-level module implements that abstraction. The high-level module receives its dependency via injection (constructor, parameter, or factory) rather than creating it internally.

The "inversion" is that the dependency arrow flips: instead of HighLevel → LowLevel, you get HighLevel → Abstraction ← LowLevel.

When to Use

  • Business logic directly instantiates infrastructure (DB, HTTP, file I/O, queues)
  • You want to unit-test high-level code without infrastructure
  • You need to swap implementations (e.g., in-memory cache vs. Redis) without changing callers
  • You are building a system where different deployments use different backends

When to Avoid

  • The low-level module is trivially simple and will never be swapped (e.g., math.sqrt)
  • You are writing a small script with no tests and no deployment variants
  • Introducing an abstraction would add complexity without any foreseeable benefit

Pseudocode

Before — high-level depends directly on low-level

CLASS EmailSender:
    METHOD send(to, subject, body):
        // Talks to SMTP server directly
        SMTP.connect("mail.example.com")
        SMTP.send(to, subject, body)

CLASS NotificationService:
    email_sender = NEW EmailSender()    // Direct dependency!

    METHOD notify(user, message):
        email_sender.send(user.email, "Alert", message)

After — both depend on an abstraction

INTERFACE MessageSender:
    METHOD send(to, subject, body)

CLASS EmailSender IMPLEMENTS MessageSender:
    METHOD send(to, subject, body):
        SMTP.connect("mail.example.com")
        SMTP.send(to, subject, body)

CLASS SMSSender IMPLEMENTS MessageSender:
    METHOD send(to, subject, body):
        SMS_API.send(to, body)

CLASS NotificationService:
    sender: MessageSender    // Depends on abstraction

    CONSTRUCTOR(sender: MessageSender):
        this.sender = sender    // Injected!

    METHOD notify(user, message):
        sender.send(user.contact, "Alert", message)

// Wiring (at the application edge):
service = NEW NotificationService(NEW EmailSender())
// or
service = NEW NotificationService(NEW SMSSender())

Python

"""
Dependency Inversion Principle — Python

BEFORE: NotificationService directly creates an EmailSender (tight coupling).
AFTER:  NotificationService depends on a MessageSender ABC; implementation is injected.
"""

from abc import ABC, abstractmethod


# ============================================================
# BEFORE: Violating DIP — direct dependency on a concrete class
# ============================================================

class EmailSenderBefore:
    def send(self, to: str, subject: str, body: str) -> None:
        print(f"  [SMTP] Sending email to {to}: '{subject}' — {body}")


class NotificationServiceBefore:
    def __init__(self):
        # Hard-coded dependency — cannot swap, cannot test in isolation
        self.sender = EmailSenderBefore()

    def notify(self, user_email: str, message: str) -> None:
        self.sender.send(user_email, "Alert", message)


# ============================================================
# AFTER: Following DIP — depend on abstraction, inject implementation
# ============================================================

class MessageSender(ABC):
    """Abstraction that both high-level and low-level code depend on."""

    @abstractmethod
    def send(self, to: str, subject: str, body: str) -> None: ...


class EmailSender(MessageSender):
    def send(self, to: str, subject: str, body: str) -> None:
        print(f"  [SMTP] Email to {to}: '{subject}' — {body}")


class SMSSender(MessageSender):
    def send(self, to: str, subject: str, body: str) -> None:
        print(f"  [SMS] Text to {to}: {body}")


class ConsoleSender(MessageSender):
    """Useful for testing — no real I/O."""

    def send(self, to: str, subject: str, body: str) -> None:
        print(f"  [CONSOLE] To={to}, Subject='{subject}', Body='{body}'")


class NotificationService:
    """High-level module — depends on MessageSender abstraction, not a concrete class."""

    def __init__(self, sender: MessageSender):
        self.sender = sender  # Injected!

    def notify(self, recipient: str, message: str) -> None:
        self.sender.send(recipient, "Alert", message)


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

def main():
    print("--- BEFORE (DIP violated) ---")
    old_service = NotificationServiceBefore()
    old_service.notify("alice@example.com", "Server is down!")

    print("\n--- AFTER (DIP applied) ---")

    print("Using EmailSender:")
    email_service = NotificationService(EmailSender())
    email_service.notify("alice@example.com", "Server is down!")

    print("\nUsing SMSSender:")
    sms_service = NotificationService(SMSSender())
    sms_service.notify("+1-555-0199", "Server is down!")

    print("\nUsing ConsoleSender (for tests):")
    test_service = NotificationService(ConsoleSender())
    test_service.notify("test-user", "Server is down!")


if __name__ == "__main__":
    main()

Go

// Dependency Inversion Principle — Go
//
// BEFORE: OrderService directly creates a PostgresDB (tight coupling).
// AFTER:  OrderService depends on a Repository interface; implementation is injected.

package main

import "fmt"

// ============================================================
// BEFORE: Violating DIP — concrete dependency
// ============================================================

type PostgresDBBefore struct{}

func (db *PostgresDBBefore) SaveOrder(id string, total float64) {
	fmt.Printf("  [POSTGRES] INSERT INTO orders (id, total) VALUES ('%s', %.2f)\n", id, total)
}

func (db *PostgresDBBefore) FindOrder(id string) string {
	return fmt.Sprintf("[POSTGRES] Order %s found", id)
}

type OrderServiceBefore struct {
	db *PostgresDBBefore // Direct dependency on concrete type!
}

func NewOrderServiceBefore() *OrderServiceBefore {
	return &OrderServiceBefore{db: &PostgresDBBefore{}}
}

func (s *OrderServiceBefore) PlaceOrder(id string, total float64) {
	fmt.Printf("  Placing order %s...\n", id)
	s.db.SaveOrder(id, total)
}

func (s *OrderServiceBefore) GetOrder(id string) {
	result := s.db.FindOrder(id)
	fmt.Printf("  %s\n", result)
}

// ============================================================
// AFTER: Following DIP — depend on interface, inject implementation
// ============================================================

// Repository is the abstraction both high-level and low-level depend on.
type Repository interface {
	Save(id string, total float64)
	Find(id string) string
}

// PostgresRepository implements Repository.
type PostgresRepository struct{}

func (r *PostgresRepository) Save(id string, total float64) {
	fmt.Printf("  [POSTGRES] INSERT INTO orders (id, total) VALUES ('%s', %.2f)\n", id, total)
}

func (r *PostgresRepository) Find(id string) string {
	return fmt.Sprintf("[POSTGRES] Order %s found", id)
}

// InMemoryRepository implements Repository — great for tests.
type InMemoryRepository struct {
	store map[string]float64
}

func NewInMemoryRepository() *InMemoryRepository {
	return &InMemoryRepository{store: make(map[string]float64)}
}

func (r *InMemoryRepository) Save(id string, total float64) {
	r.store[id] = total
	fmt.Printf("  [MEMORY] Stored order %s = %.2f\n", id, total)
}

func (r *InMemoryRepository) Find(id string) string {
	if total, ok := r.store[id]; ok {
		return fmt.Sprintf("[MEMORY] Order %s = %.2f", id, total)
	}
	return fmt.Sprintf("[MEMORY] Order %s not found", id)
}

// OrderService depends on the Repository interface — not a concrete DB.
type OrderService struct {
	repo Repository // Injected!
}

func NewOrderService(repo Repository) *OrderService {
	return &OrderService{repo: repo}
}

func (s *OrderService) PlaceOrder(id string, total float64) {
	fmt.Printf("  Placing order %s...\n", id)
	s.repo.Save(id, total)
}

func (s *OrderService) GetOrder(id string) {
	result := s.repo.Find(id)
	fmt.Printf("  %s\n", result)
}

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

func main() {
	fmt.Println("--- BEFORE (DIP violated) ---")
	oldService := NewOrderServiceBefore()
	oldService.PlaceOrder("ORD-001", 99.99)
	oldService.GetOrder("ORD-001")

	fmt.Println("\n--- AFTER (DIP applied) ---")

	fmt.Println("Using PostgresRepository:")
	pgService := NewOrderService(&PostgresRepository{})
	pgService.PlaceOrder("ORD-001", 99.99)
	pgService.GetOrder("ORD-001")

	fmt.Println("\nUsing InMemoryRepository (for tests):")
	memService := NewOrderService(NewInMemoryRepository())
	memService.PlaceOrder("ORD-002", 149.50)
	memService.GetOrder("ORD-002")
	memService.GetOrder("ORD-999") // Not found
}

JavaScript

// Dependency Inversion Principle — JavaScript
//
// BEFORE: UserService directly instantiates MySQLDatabase (tight coupling).
// AFTER:  UserService receives a database abstraction via constructor injection.

// ============================================================
// BEFORE: Violating DIP — concrete dependency baked in
// ============================================================

class MySQLDatabaseBefore {
  save(table, data) {
    console.log(`  [MYSQL] INSERT INTO ${table} SET ${JSON.stringify(data)}`);
  }

  findById(table, id) {
    console.log(`  [MYSQL] SELECT * FROM ${table} WHERE id = ${id}`);
    return { id, name: "Alice", email: "alice@example.com" };
  }
}

class UserServiceBefore {
  constructor() {
    // Hard-coded dependency — cannot swap, cannot test easily
    this.db = new MySQLDatabaseBefore();
  }

  createUser(name, email) {
    console.log(`  Creating user ${name}...`);
    this.db.save("users", { name, email });
  }

  getUser(id) {
    const user = this.db.findById("users", id);
    console.log(`  Found: ${JSON.stringify(user)}`);
  }
}

// ============================================================
// AFTER: Following DIP — depend on abstraction, inject implementation
// ============================================================

/**
 * Database "interface" (enforced by convention in JS).
 * Any implementation must provide save() and findById().
 */

class MySQLDatabase {
  save(table, data) {
    console.log(`  [MYSQL] INSERT INTO ${table} SET ${JSON.stringify(data)}`);
  }

  findById(table, id) {
    console.log(`  [MYSQL] SELECT * FROM ${table} WHERE id = ${id}`);
    return { id, name: "Alice", email: "alice@example.com" };
  }
}

class InMemoryDatabase {
  constructor() {
    this.store = new Map();
    this.nextId = 1;
  }

  save(table, data) {
    const id = this.nextId++;
    const key = `${table}:${id}`;
    const record = { id, ...data };
    this.store.set(key, record);
    console.log(`  [MEMORY] Stored ${key} = ${JSON.stringify(record)}`);
  }

  findById(table, id) {
    const key = `${table}:${id}`;
    const record = this.store.get(key) || null;
    if (record) {
      console.log(`  [MEMORY] Found ${key} = ${JSON.stringify(record)}`);
    } else {
      console.log(`  [MEMORY] ${key} not found`);
    }
    return record;
  }
}

class UserService {
  /**
   * Depends on a database abstraction — not a specific implementation.
   * @param {{ save: Function, findById: Function }} database
   */
  constructor(database) {
    this.db = database; // Injected!
  }

  createUser(name, email) {
    console.log(`  Creating user ${name}...`);
    this.db.save("users", { name, email });
  }

  getUser(id) {
    const user = this.db.findById("users", id);
    if (user) {
      console.log(`  Result: ${JSON.stringify(user)}`);
    }
  }
}

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

function main() {
  console.log("--- BEFORE (DIP violated) ---");
  const oldService = new UserServiceBefore();
  oldService.createUser("Alice", "alice@example.com");
  oldService.getUser(1);

  console.log("\n--- AFTER (DIP applied) ---");

  console.log("Using MySQLDatabase:");
  const mysqlService = new UserService(new MySQLDatabase());
  mysqlService.createUser("Alice", "alice@example.com");
  mysqlService.getUser(1);

  console.log("\nUsing InMemoryDatabase (for tests):");
  const memService = new UserService(new InMemoryDatabase());
  memService.createUser("Bob", "bob@example.com");
  memService.createUser("Carol", "carol@example.com");
  memService.getUser(1);
  memService.getUser(2);
  memService.getUser(99); // Not found
}

main();
  • Dependency Injection — DI is the practical mechanism for implementing DIP: abstractions are injected rather than created internally.
  • Repository — the repository pattern applies DIP at the data layer: business logic depends on a repository interface, not a specific database driver.
  • Adapter — adapters implement the abstraction defined by the high-level module, making low-level details conform to the required interface.
  • Hexagonal Architecture — hexagonal architecture (Ports & Adapters) is an architectural style built entirely on DIP.