tool / 52

Design Patterns

The classic Gang of Four patterns, simplified — what they are, when to use them, what they look like.

All local
12/12
Creational4
Singleton
Ensure a class has only one instance.
When: When exactly one shared resource is needed (logger, config, connection pool).
class Logger {
  static instance: Logger;
  static getInstance() {
    return this.instance ??= new Logger();
  }
}
Factory
Defer instantiation to a method.
When: When the exact type to create depends on input or context.
function shape(type) {
  if (type === "circle") return new Circle();
  if (type === "square") return new Square();
}
Builder
Construct complex objects step by step.
When: When constructors would have too many parameters.
new QueryBuilder()
  .from("users")
  .where("active", true)
  .orderBy("name")
  .build();
Prototype
Create new objects by cloning an existing one.
When: When object creation is expensive and you need many similar instances.
const base = { color: "red", size: 10 };
const copy = structuredClone(base);
Structural4
Adapter
Make incompatible interfaces work together.
When: When integrating a third-party library that doesn't match your contract.
class StripeAdapter {
  constructor(private stripe: Stripe) {}
  charge(amount) { return this.stripe.paymentIntents.create({ amount }); }
}
Decorator
Add behavior without modifying the original.
When: When you need to extend behavior dynamically and selectively.
function withLogging(fn) {
  return (...args) => {
    console.log("calling", fn.name, args);
    return fn(...args);
  };
}
Facade
Provide a simple interface to a complex subsystem.
When: When you want to hide internal complexity from consumers.
class VideoConverter {
  convert(file, format) {
    const decoded = decoder.decode(file);
    const reencoded = encoder.encode(decoded, format);
    return container.wrap(reencoded);
  }
}
Proxy
Provide a surrogate for another object.
When: For lazy loading, access control, caching, or logging.
const apiProxy = new Proxy(api, {
  get(target, prop) {
    cache[prop] ??= target[prop];
    return cache[prop];
  },
});
Behavioral4
Observer
Notify many objects when one changes.
When: Event systems, reactive UI, pub/sub.
class Emitter {
  listeners = new Set();
  on(fn) { this.listeners.add(fn); }
  emit(data) { this.listeners.forEach(fn => fn(data)); }
}
Strategy
Encapsulate interchangeable algorithms.
When: When you have multiple ways to do the same thing and want to swap them.
const sorts = {
  asc: (a, b) => a - b,
  desc: (a, b) => b - a,
};
arr.sort(sorts[direction]);
Command
Wrap a request as an object.
When: When you need undo/redo, queuing or logging of operations.
class AddCommand {
  constructor(private list, private item) {}
  execute() { this.list.push(this.item); }
  undo() { this.list.pop(); }
}
State
Change behavior when state changes.
When: When an object's behavior depends on its state and there are many states.
class Door {
  state = "closed";
  open() { if (this.state === "closed") this.state = "open"; }
  close() { if (this.state === "open") this.state = "closed"; }
}