Skip to main content

Command Palette

Search for a command to run...

design patterns

Gang of Four Design Patterns

The 23 classic object-oriented design patterns from the Gang of Four, organized by purpose with practical examples.

Gang of Four Design Patterns

TL;DR

The 23 Gang of Four (GoF) patterns are reusable solutions to common object-oriented design problems, organized into three categories: Creational (object creation), Structural (composition), and Behavioral (interaction). Know the patterns, but apply them only when they solve a real problem—not to demonstrate cleverness.

Key Takeaways

  • Creational patterns abstract object instantiation (Factory, Singleton, Builder)
  • Structural patterns compose objects into larger structures (Adapter, Facade, Decorator)
  • Behavioral patterns define object interaction (Strategy, Observer, Command)
  • Patterns have trade-offs: added abstraction vs flexibility
  • Intent matters: understand the problem before applying the pattern

Why This Matters

Design patterns provide a shared vocabulary for discussing design decisions. When a developer says "use the Strategy pattern," everyone understands the approach without lengthy explanation. However, patterns are tools, not goals. Forcing patterns into code that doesn't need them creates unnecessary complexity. The best code is often the simplest code that solves the problem.

Pattern Abuse

The most common mistake is applying patterns prematurely. If you're adding a Factory for a class that will never have multiple implementations, you're adding complexity without benefit.


Pattern Catalog Overview

GANG OF FOUR PATTERNS (23)
├── CREATIONAL (5): Object creation mechanisms
│   ├── Factory Method
│   ├── Abstract Factory
│   ├── Builder
│   ├── Prototype
│   └── Singleton
│
├── STRUCTURAL (7): Object composition
│   ├── Adapter
│   ├── Bridge
│   ├── Composite
│   ├── Decorator
│   ├── Facade
│   ├── Flyweight
│   └── Proxy
│
└── BEHAVIORAL (11): Object interaction
    ├── Chain of Responsibility
    ├── Command
    ├── Interpreter
    ├── Iterator
    ├── Mediator
    ├── Memento
    ├── Observer
    ├── State
    ├── Strategy
    ├── Template Method
    └── Visitor
Loading diagram...

Creational Patterns

Intent

Define an interface for creating objects, letting subclasses decide which class to instantiate.

Structure

┌────────────────┐       ┌────────────────┐
│    Creator     │       │    Product     │
├────────────────┤       │   (interface)  │
│ factoryMethod()│       └────────────────┘
│ operation()    │               ▲
└───────┬────────┘               │
        │                 ┌──────┴──────┐
        ▼                 │             │
┌────────────────┐  ┌─────────┐  ┌─────────┐
│ConcreteCreator │  │ProductA │  │ProductB │
├────────────────┤  └─────────┘  └─────────┘
│ factoryMethod()│
└────────────────┘

When to Use

  • Class can't anticipate the type of objects it needs to create
  • Class wants subclasses to specify the objects it creates
  • You want to localize the knowledge of which class gets created

Example

// Product interface
interface Logger {
  log(message: string): void;
}
 
// Concrete products
class ConsoleLogger implements Logger {
  log(message: string) { console.log(message); }
}
 
class FileLogger implements Logger {
  log(message: string) { /* write to file */ }
}
 
// Creator with factory method
abstract class Application {
  abstract createLogger(): Logger;
 
  run() {
    const logger = this.createLogger();
    logger.log("Application started");
  }
}
 
// Concrete creators
class DevelopmentApp extends Application {
  createLogger() { return new ConsoleLogger(); }
}
 
class ProductionApp extends Application {
  createLogger() { return new FileLogger(); }
}

Structural Patterns

Intent

Convert an interface into another interface that clients expect.

Structure

┌────────┐     ┌─────────────┐     ┌─────────────┐
│ Client │────→│   Target    │     │   Adaptee   │
└────────┘     │ (interface) │     │(existing)   │
               └──────┬──────┘     └──────┬──────┘
                      │                   │
                      ▼                   │
               ┌─────────────┐            │
               │   Adapter   │────────────┘
               └─────────────┘

When to Use

  • Use an existing class with an incompatible interface
  • Create a reusable class that works with unrelated classes
  • Need to use several existing subclasses without subclassing each

Example

// Existing class (third-party library)
class LegacyPrinter {
  printText(text: string) {
    console.log(`Printing: ${text}`);
  }
}
 
// Target interface your code expects
interface ModernPrinter {
  print(document: Document): void;
}
 
// Adapter
class PrinterAdapter implements ModernPrinter {
  constructor(private legacyPrinter: LegacyPrinter) {}
 
  print(document: Document) {
    const text = document.toString();
    this.legacyPrinter.printText(text);
  }
}

Behavioral Patterns

Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable.

Structure

┌─────────┐      ┌────────────────┐
│ Context │─────→│    Strategy    │
└─────────┘      │   (interface)  │
                 └───────┬────────┘
                         │
          ┌──────────────┼──────────────┐
          ▼              ▼              ▼
    ┌──────────┐   ┌──────────┐   ┌──────────┐
    │StrategyA │   │StrategyB │   │StrategyC │
    └──────────┘   └──────────┘   └──────────┘

When to Use

  • Many related classes differ only in behavior
  • Need different variants of an algorithm
  • Avoid exposing complex algorithm-specific data structures
  • Class has multiple conditional behaviors

Example

interface PaymentStrategy {
  pay(amount: number): void;
}
 
class CreditCardPayment implements PaymentStrategy {
  constructor(private cardNumber: string) {}
  pay(amount: number) {
    console.log(`Paid $${amount} with credit card`);
  }
}
 
class PayPalPayment implements PaymentStrategy {
  constructor(private email: string) {}
  pay(amount: number) {
    console.log(`Paid $${amount} via PayPal`);
  }
}
 
class ShoppingCart {
  constructor(private paymentStrategy: PaymentStrategy) {}
 
  checkout(amount: number) {
    this.paymentStrategy.pay(amount);
  }
 
  setPaymentStrategy(strategy: PaymentStrategy) {
    this.paymentStrategy = strategy;
  }
}
 
// Usage
const cart = new ShoppingCart(new CreditCardPayment("1234..."));
cart.checkout(100);
cart.setPaymentStrategy(new PayPalPayment("user@email.com"));
cart.checkout(50);

Pattern Selection Guide

FeatureProblemConsiderWhy
Complex object creationBuilder, FactorySeparate construction from representation
Family of related objectsAbstract FactoryEnsure compatibility
Incompatible interfacesAdapterConvert interface
Simplify complex subsystemFacadeUnified interface
Add behavior dynamicallyDecoratorAvoid subclass explosion
Control access to objectProxyLazy load, security, caching
Varying algorithmsStrategyEncapsulate algorithm family
React to state changesObserverLoose coupling
State-dependent behaviorStateState machine
Undo/redo operationsCommandEncapsulate requests

Quick Reference Card

┌─────────────────────────────────────────────────────────────┐
│              GANG OF FOUR PATTERNS CHEAT SHEET              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  CREATIONAL                                                 │
│  ─────────────────────────────────────────────────────────  │
│  Factory Method  → Subclass decides which class to create   │
│  Abstract Factory→ Create families of related objects       │
│  Builder         → Complex construction step by step        │
│  Prototype       → Clone existing objects                   │
│  Singleton       → Single instance (use sparingly!)         │
│                                                             │
│  STRUCTURAL                                                 │
│  ─────────────────────────────────────────────────────────  │
│  Adapter         → Convert interface to expected interface  │
│  Bridge          → Separate abstraction from implementation │
│  Composite       → Tree structures, uniform treatment       │
│  Decorator       → Add responsibilities dynamically         │
│  Facade          → Simplified interface to subsystem        │
│  Flyweight       → Share common state across objects        │
│  Proxy           → Placeholder controlling access           │
│                                                             │
│  BEHAVIORAL                                                 │
│  ─────────────────────────────────────────────────────────  │
│  Chain of Resp.  → Pass request along chain of handlers     │
│  Command         → Encapsulate request as object            │
│  Iterator        → Sequential access without exposure       │
│  Mediator        → Centralize complex communication         │
│  Memento         → Capture and restore state                │
│  Observer        → Notify dependents of state changes       │
│  State           → Behavior changes with state              │
│  Strategy        → Interchangeable algorithms               │
│  Template Method → Algorithm skeleton, steps in subclasses  │
│  Visitor         → New operations without changing classes  │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  RULE: Use patterns to solve problems, not to show off.     │
└─────────────────────────────────────────────────────────────┘


Sources