# Design Patterns

> Design patterns rules - creational, structural, behavioral, module pattern, composition over inheritance, common interface, inversion of control and dependency injection

- Skill: `14bryanespinoza/design-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/design-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/design-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/design-patterns

---


# Design Patterns — Rules

---

## 1. Philosophy

1. **Patterns are vocabulary** — shared language for recurring solutions, not rigid templates.
2. **Prefer composition** — object composition over class inheritance.
3. **SOLID first** — patterns emerge from SOLID principles, not replace them.
4. **YAGNI** — apply patterns when complexity demands, not preemptively.
5. **JavaScript/TypeScript native** — leverage ESM, closures, functions before classic patterns.

---

## 2. Essential Creational Patterns

### Factory Method

```ts
// factories/userFactory.ts
interface User {
  id: string;
  email: string;
  role: "admin" | "user" | "guest";
}

type UserFactory = (data: Partial<User>) => User;

export const createAdmin: UserFactory = (data) => ({
  id: crypto.randomUUID(),
  email: data.email,
  role: "admin",
});

export const createUser: UserFactory = (data) => ({
  id: crypto.randomUUID(),
  email: data.email,
  role: "user",
});

export const createGuest: UserFactory = () => ({
  id: crypto.randomUUID(),
  email: `guest-${Date.now()}@example.com`,
  role: "guest",
});
```

### Builder (for complex objects)

```ts
// builders/queryBuilder.ts
class QueryBuilder<T> {
  private filters: Record<string, unknown> = {};
  private sorts: string[] = [];
  private page = 1;
  private limit = 20;

  where(key: string, value: unknown): this {
    this.filters[key] = value;
    return this;
  }

  orderBy(field: string, dir: "asc" | "desc" = "asc"): this {
    this.sorts.push(`${field}:${dir}`);
    return this;
  }

  paginate(page: number, limit = 20): this {
    this.page = page;
    this.limit = limit;
    return this;
  }

  build(): QueryOptions<T> {
    return {
      filters: this.filters,
      sorts: this.sorts,
      page: this.page,
      limit: this.limit,
    };
  }
}

// Usage
const query = new QueryBuilder<User>()
  .where("status", "active")
  .orderBy("createdAt", "desc")
  .paginate(1, 50)
  .build();
```

### Singleton (Module-level, not class)

```ts
// services/config.ts
const config = {
  apiUrl: process.env.API_URL || "http://localhost:3000",
  timeout: 5000,
  retries: 3,
} as const;

export const configService = {
  get: <K extends keyof typeof config>(key: K) => config[key],
  set: <K extends keyof typeof config>(key: K, value: (typeof config)[K]) => {
    config[key] = value;
  },
} as const;
```

### Rules

- **Factory** — when object creation logic is complex or varies
- **Builder** — when object has many optional parameters
- **Singleton** — prefer module-level constants over class singletons
- **Avoid Abstract Factory** — rarely needed in JS/TS

---

## 3. Essential Structural Patterns

### Adapter

```ts
// adapters/paymentAdapter.ts
interface PaymentGateway {
  charge(amount: number, currency: string): Promise<ChargeResult>;
}

class StripeAdapter implements PaymentGateway {
  constructor(private stripe: StripeClient) {}

  async charge(amount: number, currency: string): Promise<ChargeResult> {
    const charge = await this.stripe.charges.create({ amount, currency });
    return { id: charge.id, status: charge.status };
  }
}

class PayPalAdapter implements PaymentGateway {
  constructor(private paypal: PayPalClient) {}

  async charge(amount: number, currency: string): Promise<ChargeResult> {
    const order = await this.paypal.createOrder({ amount, currency });
    return { id: order.id, status: order.status };
  }
}
```

### Decorator (Function Composition)

```ts
// decorators/retryDecorator.ts
function withRetry<T extends (...args: any[]) => Promise<any>>(
  fn: T,
  options: { retries: number; delay: number } = { retries: 3, delay: 1000 },
): T {
  return (async (...args) => {
    let lastError: Error;
    for (let i = 0; i <= options.retries; i++) {
      try {
        return await fn(...args);
      } catch (e) {
        lastError = e as Error;
        if (i < options.retries) await sleep(options.delay * 2 ** i);
      }
    }
    throw lastError!;
  }) as T;
}

// Usage
const fetchWithRetry = withRetry(fetchUser, { retries: 3, delay: 1000 });
```

### Proxy (Lazy Loading / Access Control)

```ts
// proxies/userProxy.ts
function createUserProxy(user: User): User {
  return new Proxy(user, {
    get(target, prop) {
      if (prop === "ssn") {
        throw new Error("Access denied: SSN is protected");
      }
      return Reflect.get(target, prop);
    },
    set(target, prop, value) {
      if (prop === "id") throw new Error("Cannot modify ID");
      return Reflect.set(target, prop, value);
    },
  });
}
```

### Rules Essential Structural Patterns

- **Adapter** — when integrating incompatible interfaces
- **Decorator** — cross-cutting concerns (logging, retry, caching)
- **Proxy** — access control, lazy loading, validation
- **Avoid Facade/Composite/Bridge** — rarely needed in modern JS/TS

---

## 4. Essential Behavioral Patterns

### Strategy

```ts
// strategies/pricingStrategy.ts
interface PricingStrategy {
  calculate(basePrice: number, context: PricingContext): Money;
}

class StandardPricing implements PricingStrategy {
  calculate(base: Money, ctx: PricingContext): Money {
    return ctx.isMember ? base.multiply(0.9) : base;
  }
}

class PromotionalPricing implements PricingStrategy {
  calculate(base: Money, ctx: PricingContext): Money {
    const discount = ctx.promoCode ? 0.2 : 0;
    return base.multiply(1 - discount);
  }
}

class PricingService {
  constructor(private strategy: PricingStrategy) {}

  setStrategy(strategy: PricingStrategy): void {
    this.strategy = strategy;
  }

  calculatePrice(base: Money, ctx: PricingContext): Money {
    return this.strategy.calculate(base, ctx);
  }
}
```

### Observer (Event Emitter)

```ts
// events/eventEmitter.ts
type EventHandler<T> = (data: T) => void | Promise<void>;

class EventEmitter<T extends Record<string, any>> {
  private handlers: Map<keyof T, Set<EventHandler<any>>> = new Map();

  on<K extends keyof T>(event: K, handler: EventHandler<T[K]>): () => void {
    const handlers = this.handlers.get(event) || new Set();
    handlers.add(handler);
    this.handlers.set(event, handlers);
    return () => handlers.delete(handler);
  }

  async emit<K extends keyof T>(event: K, data: T[K]): Promise<void> {
    const handlers = this.handlers.get(event) || new Set();
    await Promise.all([...handlers].map((h) => h(data)));
  }
}

// Usage
const events = new EventEmitter<{ userCreated: User; orderPlaced: Order }>();
const unsubscribe = events.on("userCreated", async (user) => {
  await emailService.sendWelcome(user.email);
});
```

### Command

```ts
// commands/command.ts
interface Command {
  execute(): Promise<void>;
  undo(): Promise<void>;
}

class CreateOrderCommand implements Command {
  constructor(
    private orderService: OrderService,
    private data: CreateOrderData,
  ) {}

  async execute(): Promise<void> {
    this.orderId = await this.orderService.create(this.data);
  }

  async undo(): Promise<void> {
    await this.orderService.cancel(this.orderId!);
  }
}

// Invoker
class CommandInvoker {
  private history: Command[] = [];

  async execute(command: Command): Promise<void> {
    await command.execute();
    this.history.push(command);
  }

  async undo(): Promise<void> {
    const command = this.history.pop();
    if (command) await command.undo();
  }
}
```

### Rules Essential Behavioral Patterns

- **Strategy** — when algorithm varies at runtime
- **Observer** — decoupled event notification
- **Command** — undo/redo, queuing, logging
- **Avoid State/Template Method/Visitor** — rarely needed in functional JS/TS

---

## 5. Module Pattern (ESM Native)

```ts
// modules/cache.ts
const cache = new Map<string, { value: any; expiry: number }>();

export function set(key: string, value: any, ttlMs = 60000): void {
  cache.set(key, { value, expiry: Date.now() + ttlMs });
}

export function get<T>(key: string): T | undefined {
  const entry = cache.get(key);
  if (!entry) return undefined;
  if (Date.now() > entry.expiry) {
    cache.delete(key);
    return undefined;
  }
  return entry.value as T;
}

export function clear(): void {
  cache.clear();
}

// Usage
import { set, get } from "./cache.js";
set("key", { data: "value" }, 30000);
const value = get("key");
```

### Rules ESM Native

- **ESM is the module system** — no IIFE/revealing module pattern needed
- **Named exports** — prefer over default
- **Pure functions** — no hidden state in modules
- **Tree-shakeable** — side-effect free imports

---

## 6. Composition over Inheritance

> **Reference**: see `component-design` and `clean-code` skills.

### Principle

```ts
// ❌ Inheritance (rigid)
class Animal {
  speak() {
    return "sound";
  }
}
class Dog extends Animal {
  speak() {
    return "woof";
  }
}

// ✅ Composition (flexible)
interface Speaker {
  speak(): string;
}

const dogSpeaker: Speaker = { speak: () => "woof" };
const catSpeaker: Speaker = { speak: () => "meow" };

function makeSound(speaker: Speaker) {
  console.log(speaker.speak());
}
```

### Rules Composition on Inheritance

- **Interfaces over abstract classes**
- **Function composition** — `pipe(f, g)(x)` = `g(f(x))`
- **Mixins via functions** — `withLogging(component)`
  not `class extends LoggingMixin`

---

## 7. Inversion of Control / Dependency Injection

```ts
// di/container.ts
interface ServiceRegistry {
  register<T>(token: string, factory: () => T): void;
  resolve<T>(token: string): T;
}

class Container implements ServiceRegistry {
  private services = new Map<string, { factory: () => any; instance?: any }>();
  private singletons = new Set<string>();

  register<T>(token: string, factory: () => T, singleton = false): void {
    this.services.set(token, { factory });
    if (singleton) this.singletons.add(token);
  }

  resolve<T>(token: string): T {
    const service = this.services.get(token);
    if (!service) throw new Error(`Service ${token} not registered`);

    if (this.singletons.has(token)) {
      if (!service.instance) service.instance = service.factory();
      return service.instance;
    }
    return service.factory();
  }
}

// Usage
const container = new Container();
container.register("userRepo", () => new PrismaUserRepository(), true);
container.register("emailService", () => new SendGridEmailService());

// In handler
const userRepo = container.resolve<UserRepository>("userRepo");
```

### Rules Inversion of Control

- **Explicit registration** — no magic auto-wiring
- **Singletons opt-in** — most services are transient
- **Constructor injection** — preferred over property/method injection
- **No container in domain** — only in composition root

---

## 8. Methodology

Before using ANY design pattern not documented in
this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` +
   `context7_query-docs` for design patterns, TypeScript.
2. **Official docs**: Design Patterns (GoF), Refactoring to Patterns
   (Kerievsky) — verify current relevance.
3. **Project config**: `package.json`, `tsconfig.json`
   — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against
   2 authoritative sources → DO NOT USE IT. Document as assumption or risk in
   report to orchestrator.

---

## 9. Prohibitions

- ❌ Do not use Singleton class — module-level is sufficient
- ❌ Do not use Abstract Factory — rarely needed
- ❌ Do not use inheritance for code reuse — composition
- ❌ Do not create God objects — single responsibility
- ❌ Do not use patterns as checkboxes — solve problems, not apply patterns
- ❌ Do not use Observer for simple callbacks — use functions
- ❌ Do not use Visitor — pattern matching / discriminated unions instead
- ❌ Do not use Template Method — higher-order functions instead

---

## 10. References

> **Note:** For JavaScript conventions, see [JavaScript](../javascript/SKILL.md)
> **Note:** For TypeScript rules, see [TypeScript](../typescript/SKILL.md)
> **Note:** For Component patterns, see
> [Component Design](../component-design/SKILL.md)
> **Note:** For Clean Code, see [Clean Code](../clean-code/SKILL.md)
> **Note:** For DDD patterns, see [DDD](../ddd/SKILL.md)

---

Last updated: 2026-08

