behavioral

star 10

Behavioral design patterns from the Gang of Four — Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, and Visitor. Patterns that manage algorithms, relationships, and responsibilities between objects. USE FOR: object communication, event handling, state management, algorithm encapsulation, request handling chains, undo/redo, publish-subscribe DO NOT USE FOR: object creation (use creational), structural composition (use structural)

Tyler-R-Kendrick By Tyler-R-Kendrick schedule Updated 2/11/2026

name: behavioral description: | Behavioral design patterns from the Gang of Four — Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, and Visitor. Patterns that manage algorithms, relationships, and responsibilities between objects. USE FOR: object communication, event handling, state management, algorithm encapsulation, request handling chains, undo/redo, publish-subscribe DO NOT USE FOR: object creation (use creational), structural composition (use structural) license: MIT metadata: displayName: "Behavioral Patterns" author: "Tyler-R-Kendrick" compatibility: claude, copilot, cursor references:


Behavioral Design Patterns

Overview

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. They describe not just patterns of objects or classes but also the patterns of communication between them. These patterns characterize complex control flow that is difficult to follow at run-time — they shift your focus away from flow of control to the way objects are interconnected.


1. Chain of Responsibility

Intent

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

Structure

┌──────────────────┐
│    Handler        │◀────────────┐
│    (interface)    │             │ next
├──────────────────┤             │
│ + handle(req)     │─────────────┘
│ + setNext(h)      │
└──────┬───────────┘
       │ implements
       ├──────────────┬──────────────┐
       ▼              ▼              ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ HandlerA    │ │ HandlerB    │ │ HandlerC    │
└────────────┘ └────────────┘ └────────────┘

Participants

  • Handler — defines an interface for handling requests; optionally links to a successor
  • ConcreteHandler — handles requests it is responsible for; forwards unhandled requests to the next handler
  • Client — initiates the request to a handler in the chain

When to Use

  • More than one object may handle a request, and the handler is not known a priori
  • You want to issue a request to one of several objects without specifying the receiver explicitly
  • The set of handlers should be configurable dynamically (e.g., middleware pipelines)

TypeScript Example

// Handler interface
interface SupportHandler {
  setNext(handler: SupportHandler): SupportHandler;
  handle(issue: { level: string; message: string }): string;
}

// Base handler
abstract class BaseSupportHandler implements SupportHandler {
  private next: SupportHandler | null = null;

  setNext(handler: SupportHandler): SupportHandler {
    this.next = handler;
    return handler;
  }

  handle(issue: { level: string; message: string }): string {
    if (this.next) {
      return this.next.handle(issue);
    }
    return `No handler found for: ${issue.message}`;
  }
}

// Concrete handlers
class TierOneSupport extends BaseSupportHandler {
  handle(issue: { level: string; message: string }): string {
    if (issue.level === "basic") {
      return `[Tier 1] Resolved: ${issue.message}`;
    }
    return super.handle(issue);
  }
}

class TierTwoSupport extends BaseSupportHandler {
  handle(issue: { level: string; message: string }): string {
    if (issue.level === "intermediate") {
      return `[Tier 2] Resolved: ${issue.message}`;
    }
    return super.handle(issue);
  }
}

class EngineeringSupport extends BaseSupportHandler {
  handle(issue: { level: string; message: string }): string {
    if (issue.level === "critical") {
      return `[Engineering] Resolved: ${issue.message}`;
    }
    return super.handle(issue);
  }
}

// Usage — build the chain
const tier1 = new TierOneSupport();
const tier2 = new TierTwoSupport();
const engineering = new EngineeringSupport();

tier1.setNext(tier2).setNext(engineering);

console.log(tier1.handle({ level: "basic", message: "Password reset" }));
// [Tier 1] Resolved: Password reset
console.log(tier1.handle({ level: "critical", message: "Data corruption" }));
// [Engineering] Resolved: Data corruption

2. Command

Intent

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

Structure

┌──────────┐       ┌──────────────────┐       ┌──────────────┐
│  Invoker  │──────▶│    Command        │       │   Receiver    │
│           │       │    (interface)    │       ├──────────────┤
└──────────┘       ├──────────────────┤       │ + action()    │
                    │ + execute()       │       └──────┬───────┘
                    │ + undo()          │              │
                    └──────┬───────────┘              │
                           │ implements                │
                           ▼                           │
                    ┌──────────────────┐              │
                    │ ConcreteCommand   │──────────────┘
                    ├──────────────────┤   calls
                    │ - receiver        │
                    │ - state           │
                    │ + execute()       │
                    │ + undo()          │
                    └──────────────────┘

Participants

  • Command — declares an interface for executing an operation
  • ConcreteCommand — binds a Receiver to an action; implements execute/undo
  • Invoker — asks the command to carry out the request
  • Receiver — knows how to perform the operations associated with the request

When to Use

  • You need to parameterize objects with an action to perform
  • You need to specify, queue, and execute requests at different times
  • You need to support undo/redo
  • You need to support logging changes so they can be reapplied after a crash

TypeScript Example

// Receiver
class TextEditor {
  content = "";

  insert(text: string, position: number): void {
    this.content = this.content.slice(0, position) + text + this.content.slice(position);
  }

  delete(position: number, length: number): string {
    const deleted = this.content.slice(position, position + length);
    this.content = this.content.slice(0, position) + this.content.slice(position + length);
    return deleted;
  }
}

// Command interface
interface EditorCommand {
  execute(): void;
  undo(): void;
}

// Concrete commands
class InsertCommand implements EditorCommand {
  constructor(
    private editor: TextEditor,
    private text: string,
    private position: number
  ) {}

  execute(): void {
    this.editor.insert(this.text, this.position);
  }

  undo(): void {
    this.editor.delete(this.position, this.text.length);
  }
}

class DeleteCommand implements EditorCommand {
  private deletedText = "";

  constructor(
    private editor: TextEditor,
    private position: number,
    private length: number
  ) {}

  execute(): void {
    this.deletedText = this.editor.delete(this.position, this.length);
  }

  undo(): void {
    this.editor.insert(this.deletedText, this.position);
  }
}

// Invoker with undo/redo
class CommandHistory {
  private undoStack: EditorCommand[] = [];
  private redoStack: EditorCommand[] = [];

  execute(command: EditorCommand): void {
    command.execute();
    this.undoStack.push(command);
    this.redoStack = []; // clear redo on new action
  }

  undo(): void {
    const cmd = this.undoStack.pop();
    if (cmd) {
      cmd.undo();
      this.redoStack.push(cmd);
    }
  }

  redo(): void {
    const cmd = this.redoStack.pop();
    if (cmd) {
      cmd.execute();
      this.undoStack.push(cmd);
    }
  }
}

// Usage
const editor = new TextEditor();
const history = new CommandHistory();

history.execute(new InsertCommand(editor, "Hello", 0));
history.execute(new InsertCommand(editor, " World", 5));
console.log(editor.content); // "Hello World"

history.undo();
console.log(editor.content); // "Hello"

history.redo();
console.log(editor.content); // "Hello World"

3. Iterator

Intent

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

Structure

┌──────────────────┐        ┌──────────────────┐
│   Aggregate       │───────▶│    Iterator       │
│   (interface)     │creates │    (interface)    │
├──────────────────┤        ├──────────────────┤
│ + createIterator()│        │ + hasNext(): bool │
└──────────────────┘        │ + next(): T       │
                             └──────────────────┘

Participants

  • Iterator — defines an interface for accessing and traversing elements
  • ConcreteIterator — implements the Iterator interface; tracks the current position
  • Aggregate — defines an interface for creating an Iterator object
  • ConcreteAggregate — returns an instance of the ConcreteIterator

When to Use

  • You want to access a collection's elements without exposing its internal structure
  • You want to support multiple traversals of the same collection
  • You want a uniform interface for traversing different types of collections

TypeScript Example

// Iterator interface
interface Iterator<T> {
  hasNext(): boolean;
  next(): T;
  reset(): void;
}

// Concrete collection with custom iterator
class TreeNode<T> {
  children: TreeNode<T>[] = [];
  constructor(public value: T) {}

  add(...nodes: TreeNode<T>[]): this {
    this.children.push(...nodes);
    return this;
  }
}

// Depth-first iterator
class DepthFirstIterator<T> implements Iterator<T> {
  private stack: TreeNode<T>[];

  constructor(private root: TreeNode<T>) {
    this.stack = [root];
  }

  hasNext(): boolean {
    return this.stack.length > 0;
  }

  next(): T {
    if (!this.hasNext()) throw new Error("No more elements");
    const node = this.stack.pop()!;
    // Push children in reverse so left child is processed first
    for (let i = node.children.length - 1; i >= 0; i--) {
      this.stack.push(node.children[i]);
    }
    return node.value;
  }

  reset(): void {
    this.stack = [this.root];
  }
}

// Breadth-first iterator
class BreadthFirstIterator<T> implements Iterator<T> {
  private queue: TreeNode<T>[];

  constructor(private root: TreeNode<T>) {
    this.queue = [root];
  }

  hasNext(): boolean {
    return this.queue.length > 0;
  }

  next(): T {
    if (!this.hasNext()) throw new Error("No more elements");
    const node = this.queue.shift()!;
    this.queue.push(...node.children);
    return node.value;
  }

  reset(): void {
    this.queue = [this.root];
  }
}

// Usage
const tree = new TreeNode("A")
  .add(
    new TreeNode("B").add(new TreeNode("D"), new TreeNode("E")),
    new TreeNode("C").add(new TreeNode("F"))
  );

const dfs = new DepthFirstIterator(tree);
const dfsResult: string[] = [];
while (dfs.hasNext()) dfsResult.push(dfs.next());
console.log("DFS:", dfsResult.join(" -> ")); // A -> B -> D -> E -> C -> F

const bfs = new BreadthFirstIterator(tree);
const bfsResult: string[] = [];
while (bfs.hasNext()) bfsResult.push(bfs.next());
console.log("BFS:", bfsResult.join(" -> ")); // A -> B -> C -> D -> E -> F

4. Mediator

Intent

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

Structure

┌──────────────────┐        ┌──────────────────┐
│    Mediator       │◀───────│   Colleague       │
│    (interface)    │        │   (interface)     │
├──────────────────┤        ├──────────────────┤
│ + notify(sender,  │        │ - mediator        │
│         event)    │        └──────┬───────────┘
└──────┬───────────┘               │ implements
       │ implements                 ├──────────┐
       ▼                           ▼          ▼
┌──────────────────┐       ┌──────────┐┌──────────┐
│ ConcreteMediator  │       │ColleagueA││ColleagueB│
│                   │       └──────────┘└──────────┘
│ - colleagueA      │
│ - colleagueB      │
└──────────────────┘

Participants

  • Mediator — defines an interface for communicating with Colleague objects
  • ConcreteMediator — implements cooperative behavior by coordinating Colleague objects
  • Colleague — each Colleague knows its Mediator and communicates with it instead of other Colleagues

When to Use

  • A set of objects communicate in well-defined but complex ways
  • Reusing an object is difficult because it refers to and communicates with many other objects
  • A behavior distributed between several classes should be customizable without subclassing

TypeScript Example

// Mediator interface
interface ChatMediator {
  sendMessage(message: string, sender: ChatUser): void;
  addUser(user: ChatUser): void;
}

// Colleague
class ChatUser {
  private messages: string[] = [];

  constructor(
    public name: string,
    private mediator: ChatMediator
  ) {
    mediator.addUser(this);
  }

  send(message: string): void {
    console.log(`${this.name} sends: ${message}`);
    this.mediator.sendMessage(message, this);
  }

  receive(message: string, from: string): void {
    const formatted = `${from}: ${message}`;
    this.messages.push(formatted);
    console.log(`  ${this.name} received <- ${formatted}`);
  }

  getHistory(): string[] {
    return [...this.messages];
  }
}

// Concrete mediator
class ChatRoom implements ChatMediator {
  private users: ChatUser[] = [];

  addUser(user: ChatUser): void {
    this.users.push(user);
  }

  sendMessage(message: string, sender: ChatUser): void {
    for (const user of this.users) {
      if (user !== sender) {
        user.receive(message, sender.name);
      }
    }
  }
}

// Usage — users never reference each other directly
const room = new ChatRoom();
const alice = new ChatUser("Alice", room);
const bob = new ChatUser("Bob", room);
const charlie = new ChatUser("Charlie", room);

alice.send("Hello everyone!");
// Alice sends: Hello everyone!
//   Bob received <- Alice: Hello everyone!
//   Charlie received <- Alice: Hello everyone!

bob.send("Hey Alice!");
// Bob sends: Hey Alice!
//   Alice received <- Bob: Hey Alice!
//   Charlie received <- Bob: Hey Alice!

5. Memento

Intent

Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.

Structure

┌──────────────┐     ┌──────────────────┐     ┌──────────────┐
│  Caretaker    │────▶│    Memento        │◀────│  Originator   │
├──────────────┤keeps│    (opaque)       │saves├──────────────┤
│ - mementos[]  │     ├──────────────────┤     │ - state       │
└──────────────┘     │ + getState()      │     │ + save()      │
                      └──────────────────┘     │ + restore(m)  │
                                                └──────────────┘

Participants

  • Memento — stores internal state of the Originator; protects against access by objects other than the Originator
  • Originator — creates a Memento containing a snapshot of its current state; uses a Memento to restore
  • Caretaker — responsible for keeping the Memento; never operates on or examines its contents

When to Use

  • A snapshot of an object's state must be saved so it can be restored later
  • A direct interface to obtaining the state would expose implementation details and break encapsulation

TypeScript Example

// Memento
class EditorMemento {
  constructor(
    private readonly content: string,
    private readonly cursorPos: number,
    private readonly timestamp: Date
  ) {}

  getContent(): string { return this.content; }
  getCursorPos(): number { return this.cursorPos; }
  getTimestamp(): Date { return this.timestamp; }
}

// Originator
class DocumentEditor {
  private content = "";
  private cursorPos = 0;

  type(text: string): void {
    this.content =
      this.content.slice(0, this.cursorPos) +
      text +
      this.content.slice(this.cursorPos);
    this.cursorPos += text.length;
  }

  moveCursor(pos: number): void {
    this.cursorPos = Math.max(0, Math.min(pos, this.content.length));
  }

  save(): EditorMemento {
    return new EditorMemento(this.content, this.cursorPos, new Date());
  }

  restore(memento: EditorMemento): void {
    this.content = memento.getContent();
    this.cursorPos = memento.getCursorPos();
  }

  toString(): string {
    return `"${this.content}" (cursor@${this.cursorPos})`;
  }
}

// Caretaker
class EditorHistory {
  private snapshots: EditorMemento[] = [];

  push(memento: EditorMemento): void {
    this.snapshots.push(memento);
  }

  pop(): EditorMemento | undefined {
    return this.snapshots.pop();
  }
}

// Usage
const doc = new DocumentEditor();
const history = new EditorHistory();

doc.type("Hello");
history.push(doc.save());

doc.type(" World");
history.push(doc.save());

doc.type("!!!");
console.log(doc.toString()); // "Hello World!!!" (cursor@14)

doc.restore(history.pop()!);
console.log(doc.toString()); // "Hello World" (cursor@11)

doc.restore(history.pop()!);
console.log(doc.toString()); // "Hello" (cursor@5)

6. Observer

Intent

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Structure

┌──────────────────┐         ┌──────────────────┐
│    Subject        │────────▶│    Observer       │
├──────────────────┤ notifies│    (interface)    │
│ - observers[]     │         ├──────────────────┤
│ + attach(obs)     │         │ + update(data)    │
│ + detach(obs)     │         └──────┬───────────┘
│ + notify()        │                │ implements
└──────────────────┘                ▼
                              ┌──────────────────┐
                              │ ConcreteObserver   │
                              ├──────────────────┤
                              │ + update(data)    │
                              └──────────────────┘

Participants

  • Subject — knows its observers; provides an interface for attaching and detaching Observer objects
  • Observer — defines an updating interface for objects that should be notified of changes
  • ConcreteSubject — stores state of interest; sends a notification when state changes
  • ConcreteObserver — maintains a reference to a ConcreteSubject; implements the update interface

When to Use

  • When a change to one object requires changing others, and you do not know how many objects need to change
  • When an object should notify other objects without making assumptions about who those objects are

TypeScript Example

// Observer interface
interface EventListener<T> {
  update(event: string, data: T): void;
}

// Subject — generic event emitter
class EventEmitter<T> {
  private listeners = new Map<string, Set<EventListener<T>>>();

  subscribe(event: string, listener: EventListener<T>): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(listener);

    // Return unsubscribe function
    return () => this.listeners.get(event)?.delete(listener);
  }

  protected notify(event: string, data: T): void {
    this.listeners.get(event)?.forEach(listener => listener.update(event, data));
  }
}

// Concrete subject
class Store extends EventEmitter<{ key: string; value: unknown }> {
  private state = new Map<string, unknown>();

  set(key: string, value: unknown): void {
    const old = this.state.get(key);
    this.state.set(key, value);
    this.notify("change", { key, value });
    if (old === undefined) {
      this.notify("add", { key, value });
    }
  }

  get(key: string): unknown {
    return this.state.get(key);
  }
}

// Concrete observers
class LoggingObserver implements EventListener<{ key: string; value: unknown }> {
  update(event: string, data: { key: string; value: unknown }): void {
    console.log(`[LOG] ${event}: ${data.key} = ${JSON.stringify(data.value)}`);
  }
}

class ValidationObserver implements EventListener<{ key: string; value: unknown }> {
  update(event: string, data: { key: string; value: unknown }): void {
    if (data.key === "email" && typeof data.value === "string") {
      if (!data.value.includes("@")) {
        console.log(`[WARN] Invalid email: ${data.value}`);
      }
    }
  }
}

// Usage
const store = new Store();
const unsubLog = store.subscribe("change", new LoggingObserver());
store.subscribe("change", new ValidationObserver());

store.set("name", "Alice");    // [LOG] change: name = "Alice"
store.set("email", "invalid"); // [LOG] change: email = "invalid"
                                // [WARN] Invalid email: invalid

unsubLog(); // unsubscribe logger
store.set("name", "Bob");      // (no log output, validation still active)

7. State

Intent

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

Structure

┌──────────────────┐        ┌──────────────────┐
│    Context        │───────▶│     State         │
├──────────────────┤ has-a  │    (interface)    │
│ - state: State    │        ├──────────────────┤
│ + request()       │        │ + handle(ctx)     │
└──────────────────┘        └──────┬───────────┘
                                   │ implements
                            ┌──────┼──────────┐
                            ▼      ▼          ▼
                     ┌─────────┐┌─────────┐┌─────────┐
                     │ StateA   ││ StateB   ││ StateC   │
                     └─────────┘└─────────┘└─────────┘

Participants

  • Context — maintains an instance of a ConcreteState subclass that defines the current state
  • State — defines an interface for encapsulating the behavior associated with a particular state
  • ConcreteState — each subclass implements behavior associated with a state of the Context

When to Use

  • An object's behavior depends on its state, and it must change behavior at runtime
  • Operations have large multipart conditional statements that depend on the object's state

TypeScript Example

// State interface
interface OrderState {
  next(order: Order): void;
  cancel(order: Order): void;
  toString(): string;
}

// Context
class Order {
  private state: OrderState;

  constructor(public readonly id: string) {
    this.state = new PendingState();
  }

  setState(state: OrderState): void {
    console.log(`  Order ${this.id}: ${this.state} -> ${state}`);
    this.state = state;
  }

  next(): void { this.state.next(this); }
  cancel(): void { this.state.cancel(this); }
  getStatus(): string { return this.state.toString(); }
}

// Concrete states
class PendingState implements OrderState {
  next(order: Order): void { order.setState(new PaidState()); }
  cancel(order: Order): void { order.setState(new CancelledState()); }
  toString(): string { return "PENDING"; }
}

class PaidState implements OrderState {
  next(order: Order): void { order.setState(new ShippedState()); }
  cancel(order: Order): void {
    console.log(`  Refunding order ${order.id}...`);
    order.setState(new CancelledState());
  }
  toString(): string { return "PAID"; }
}

class ShippedState implements OrderState {
  next(order: Order): void { order.setState(new DeliveredState()); }
  cancel(order: Order): void {
    console.log(`  Cannot cancel — order ${order.id} already shipped`);
  }
  toString(): string { return "SHIPPED"; }
}

class DeliveredState implements OrderState {
  next(order: Order): void {
    console.log(`  Order ${order.id} already delivered`);
  }
  cancel(order: Order): void {
    console.log(`  Cannot cancel — order ${order.id} already delivered`);
  }
  toString(): string { return "DELIVERED"; }
}

class CancelledState implements OrderState {
  next(order: Order): void {
    console.log(`  Order ${order.id} is cancelled — no further transitions`);
  }
  cancel(order: Order): void {
    console.log(`  Order ${order.id} is already cancelled`);
  }
  toString(): string { return "CANCELLED"; }
}

// Usage
const order = new Order("ORD-001");
order.next();   // PENDING -> PAID
order.next();   // PAID -> SHIPPED
order.cancel(); // Cannot cancel — already shipped
order.next();   // SHIPPED -> DELIVERED

8. Strategy

Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Structure

┌──────────────────┐        ┌──────────────────┐
│    Context        │───────▶│    Strategy       │
├──────────────────┤ has-a  │    (interface)    │
│ - strategy        │        ├──────────────────┤
│ + execute()       │        │ + execute(data)   │
└──────────────────┘        └──────┬───────────┘
                                   │ implements
                            ┌──────┼──────────┐
                            ▼      ▼          ▼
                     ┌────────┐┌────────┐┌────────┐
                     │ StratA  ││ StratB  ││ StratC  │
                     └────────┘└────────┘└────────┘

Participants

  • Strategy — declares an interface common to all supported algorithms
  • ConcreteStrategy — implements the algorithm using the Strategy interface
  • Context — is configured with a ConcreteStrategy; delegates to its Strategy

When to Use

  • Many related classes differ only in their behavior
  • You need different variants of an algorithm
  • An algorithm uses data that clients should not know about
  • A class defines many behaviors, and these appear as multiple conditional statements

TypeScript Example

// Strategy interface
interface CompressionStrategy {
  compress(data: string): string;
  name: string;
}

// Concrete strategies
class GzipStrategy implements CompressionStrategy {
  name = "gzip";
  compress(data: string): string {
    return `[gzip:${data.length}]${data.substring(0, 10)}...`;
  }
}

class BrotliStrategy implements CompressionStrategy {
  name = "brotli";
  compress(data: string): string {
    return `[br:${data.length}]${data.substring(0, 8)}...`;
  }
}

class NoCompressionStrategy implements CompressionStrategy {
  name = "none";
  compress(data: string): string {
    return data;
  }
}

// Context
class FileCompressor {
  constructor(private strategy: CompressionStrategy) {}

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

  compressFile(filename: string, content: string): string {
    const result = this.strategy.compress(content);
    return `${filename} compressed with ${this.strategy.name}: ${result}`;
  }
}

// Usage — swap algorithms at runtime
const compressor = new FileCompressor(new GzipStrategy());
console.log(compressor.compressFile("data.json", "a]".repeat(1000)));

compressor.setStrategy(new BrotliStrategy());
console.log(compressor.compressFile("data.json", "a".repeat(1000)));

// Strategy selection based on file size
function selectStrategy(size: number): CompressionStrategy {
  if (size > 10000) return new BrotliStrategy();
  if (size > 1000) return new GzipStrategy();
  return new NoCompressionStrategy();
}

9. Template Method

Intent

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

Structure

┌──────────────────────┐
│   AbstractClass       │
├──────────────────────┤
│ + templateMethod()    │  ← calls step1, step2, step3 in order
│ + step1()            │  ← may have default implementation
│ + step2()            │  ← abstract — subclass MUST implement
│ + step3()            │  ← hook — subclass CAN override
└──────┬───────────────┘
       │ extends
       ▼
┌──────────────────────┐
│   ConcreteClass       │
├──────────────────────┤
│ + step2()            │  ← implements required step
│ + step3()            │  ← optionally overrides hook
└──────────────────────┘

Participants

  • AbstractClass — defines the template method (the skeleton); declares abstract steps and optional hooks
  • ConcreteClass — implements the abstract steps; optionally overrides hooks

When to Use

  • You want to implement the invariant parts of an algorithm once and let subclasses fill in the varying parts
  • You want to control which parts of an algorithm subclasses can override (hooks vs. required steps)
  • Common behavior among subclasses should be factored and localized in a common class to avoid duplication

TypeScript Example

// Abstract class with template method
abstract class DataPipeline {
  // Template method — defines the skeleton
  run(source: string): string {
    const raw = this.extract(source);
    const validated = this.validate(raw);
    const transformed = this.transform(validated);
    this.beforeLoad(transformed); // hook
    const result = this.load(transformed);
    this.afterLoad(result); // hook
    return result;
  }

  // Abstract steps — subclasses MUST implement
  protected abstract extract(source: string): string[];
  protected abstract transform(data: string[]): string[];
  protected abstract load(data: string[]): string;

  // Default step — can be overridden
  protected validate(data: string[]): string[] {
    return data.filter(item => item.trim().length > 0);
  }

  // Hooks — do nothing by default, subclasses CAN override
  protected beforeLoad(_data: string[]): void {}
  protected afterLoad(_result: string): void {}
}

// Concrete implementation
class CsvToJsonPipeline extends DataPipeline {
  protected extract(source: string): string[] {
    return source.split("\n");
  }

  protected transform(data: string[]): string[] {
    const headers = data[0].split(",");
    return data.slice(1).map(row => {
      const values = row.split(",");
      const obj: Record<string, string> = {};
      headers.forEach((h, i) => obj[h.trim()] = values[i]?.trim() ?? "");
      return JSON.stringify(obj);
    });
  }

  protected load(data: string[]): string {
    return `[${data.join(",")}]`;
  }

  protected afterLoad(result: string): void {
    console.log(`Loaded ${JSON.parse(result).length} records`);
  }
}

// Usage
const pipeline = new CsvToJsonPipeline();
const csv = "name,age\nAlice,30\nBob,25\n";
const json = pipeline.run(csv);
console.log(json); // [{"name":"Alice","age":"30"},{"name":"Bob","age":"25"}]
// Loaded 2 records

10. Visitor

Intent

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Structure

┌──────────────────┐       ┌──────────────────────┐
│    Visitor        │       │     Element           │
│    (interface)    │       │     (interface)       │
├──────────────────┤       ├──────────────────────┤
│ + visitA(elemA)   │       │ + accept(visitor)     │
│ + visitB(elemB)   │       └──────┬───────────────┘
└──────┬───────────┘              │ implements
       │ implements                ├──────────┐
       ▼                          ▼          ▼
┌────────────────┐         ┌──────────┐┌──────────┐
│ConcreteVisitor  │         │ ElementA  ││ ElementB  │
├────────────────┤         ├──────────┤├──────────┤
│ + visitA(elemA) │         │ accept(v) ││ accept(v) │
│ + visitB(elemB) │         │{v.visitA} ││{v.visitB} │
└────────────────┘         └──────────┘└──────────┘

Participants

  • Visitor — declares a visit operation for each class of ConcreteElement
  • ConcreteVisitor — implements each visit operation
  • Element — defines an accept(visitor) method
  • ConcreteElement — implements accept by calling the appropriate visitor method

When to Use

  • An object structure contains many classes with differing interfaces, and you want to perform operations that depend on their concrete classes
  • Many distinct and unrelated operations need to be performed on objects in a structure, and you want to avoid "polluting" their classes
  • The classes in the structure rarely change, but you often define new operations over the structure

TypeScript Example

// Visitor interface
interface ASTVisitor<R> {
  visitNumber(node: NumberNode): R;
  visitBinaryOp(node: BinaryOpNode): R;
  visitUnaryOp(node: UnaryOpNode): R;
}

// Element interface
interface ASTNode {
  accept<R>(visitor: ASTVisitor<R>): R;
}

// Concrete elements
class NumberNode implements ASTNode {
  constructor(public value: number) {}

  accept<R>(visitor: ASTVisitor<R>): R {
    return visitor.visitNumber(this);
  }
}

class BinaryOpNode implements ASTNode {
  constructor(
    public operator: "+" | "-" | "*" | "/",
    public left: ASTNode,
    public right: ASTNode
  ) {}

  accept<R>(visitor: ASTVisitor<R>): R {
    return visitor.visitBinaryOp(this);
  }
}

class UnaryOpNode implements ASTNode {
  constructor(public operator: "-", public operand: ASTNode) {}

  accept<R>(visitor: ASTVisitor<R>): R {
    return visitor.visitUnaryOp(this);
  }
}

// Concrete visitor — evaluator
class EvaluatorVisitor implements ASTVisitor<number> {
  visitNumber(node: NumberNode): number { return node.value; }

  visitBinaryOp(node: BinaryOpNode): number {
    const l = node.left.accept(this);
    const r = node.right.accept(this);
    switch (node.operator) {
      case "+": return l + r;
      case "-": return l - r;
      case "*": return l * r;
      case "/": return l / r;
    }
  }

  visitUnaryOp(node: UnaryOpNode): number {
    return -node.operand.accept(this);
  }
}

// Concrete visitor — printer
class PrinterVisitor implements ASTVisitor<string> {
  visitNumber(node: NumberNode): string { return String(node.value); }

  visitBinaryOp(node: BinaryOpNode): string {
    return `(${node.left.accept(this)} ${node.operator} ${node.right.accept(this)})`;
  }

  visitUnaryOp(node: UnaryOpNode): string {
    return `(-${node.operand.accept(this)})`;
  }
}

// Usage — same AST, different operations without modifying nodes
// Represents: (3 + 4) * (-2)
const ast = new BinaryOpNode("*",
  new BinaryOpNode("+", new NumberNode(3), new NumberNode(4)),
  new UnaryOpNode("-", new NumberNode(2))
);

console.log(ast.accept(new PrinterVisitor()));    // ((3 + 4) * (-2))
console.log(ast.accept(new EvaluatorVisitor()));  // -14

11. Interpreter

Intent

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

Note on Usage

The Interpreter pattern is the least commonly used GoF pattern in modern practice. Most real-world expression evaluation is handled by parser generators, embedded scripting engines (Lua, JS), or expression libraries. Use Interpreter only for simple, well-defined grammars (e.g., query filters, configuration rules).

Structure

┌───────────────────────┐
│  AbstractExpression    │
│  (interface)           │
├───────────────────────┤
│ + interpret(context)   │
└──────┬────────────────┘
       │ implements
       ├──────────────────┐
       ▼                  ▼
┌──────────────────┐ ┌──────────────────┐
│TerminalExpression │ │NonterminalExpr.  │
│(literal values)   │ │(rules/operators) │
└──────────────────┘ └──────────────────┘

When to Use

  • The grammar is simple and efficiency is not a critical concern
  • You want to evaluate simple rule expressions, search filters, or configuration DSLs

TypeScript Example

// Context
interface InterpretContext {
  variables: Record<string, boolean>;
}

// Abstract expression
interface BoolExpression {
  interpret(ctx: InterpretContext): boolean;
  toString(): string;
}

// Terminal expression
class Variable implements BoolExpression {
  constructor(private name: string) {}
  interpret(ctx: InterpretContext): boolean {
    return ctx.variables[this.name] ?? false;
  }
  toString(): string { return this.name; }
}

// Nonterminal expressions
class AndExpression implements BoolExpression {
  constructor(private left: BoolExpression, private right: BoolExpression) {}
  interpret(ctx: InterpretContext): boolean {
    return this.left.interpret(ctx) && this.right.interpret(ctx);
  }
  toString(): string { return `(${this.left} AND ${this.right})`; }
}

class OrExpression implements BoolExpression {
  constructor(private left: BoolExpression, private right: BoolExpression) {}
  interpret(ctx: InterpretContext): boolean {
    return this.left.interpret(ctx) || this.right.interpret(ctx);
  }
  toString(): string { return `(${this.left} OR ${this.right})`; }
}

class NotExpression implements BoolExpression {
  constructor(private expr: BoolExpression) {}
  interpret(ctx: InterpretContext): boolean {
    return !this.expr.interpret(ctx);
  }
  toString(): string { return `NOT(${this.expr})`; }
}

// Usage — evaluate: (isAdmin OR (isActive AND NOT(isBanned)))
const expr = new OrExpression(
  new Variable("isAdmin"),
  new AndExpression(
    new Variable("isActive"),
    new NotExpression(new Variable("isBanned"))
  )
);

console.log(`Rule: ${expr}`);
console.log(expr.interpret({ variables: { isAdmin: false, isActive: true, isBanned: false } })); // true
console.log(expr.interpret({ variables: { isAdmin: false, isActive: true, isBanned: true } }));  // false
console.log(expr.interpret({ variables: { isAdmin: true, isActive: false, isBanned: true } }));  // true

Commonly Confused Pairs

Strategy vs State

Aspect Strategy State
Intent Swap an algorithm from outside Change behavior as internal state changes
Who decides The client selects the strategy The state objects trigger transitions
Awareness Strategies are unaware of each other States often know about sibling states
Typical trigger Client calls setStrategy() Internal condition triggers setState()
Example Choose sort algorithm Order status lifecycle (Pending -> Paid -> Shipped)

Rule of thumb: If the client picks the behavior, it is Strategy. If the object changes its own behavior based on internal conditions, it is State.

Command vs Memento

Aspect Command Memento
Intent Encapsulate an action as an object Capture a snapshot of state
What it stores An operation + its parameters An object's internal state
Undo mechanism Reverse the operation (execute inverse) Restore the saved state
Scope One action at a time Full state snapshot
Example "Insert text at position 5" (undo = delete) "Save entire document state" (undo = restore)

Rule of thumb: Command stores what to do (and how to undo it). Memento stores what things looked like (so you can go back).

Observer vs Mediator

Aspect Observer Mediator
Direction One-to-many (subject -> observers) Many-to-many (colleagues <-> mediator)
Coupling Observers subscribe to a subject Colleagues only know the mediator
Awareness Subject does not know observer types Mediator knows all colleague types
Typical use Event notification, data binding Complex UI interactions, chat rooms
Distribution Distributed — each subject manages its list Centralized — one mediator coordinates all

Rule of thumb: Observer is for broadcasting events. Mediator is for coordinating complex interactions between many objects.


Full Comparison Table

Pattern Key Mechanism Problem It Solves
Chain of Responsibility Linked handler chain Decouple sender from receiver; multiple potential handlers
Command Request as object Parameterize, queue, log, undo operations
Interpreter Grammar tree evaluation Evaluate simple expressions and DSLs
Iterator Sequential traversal interface Access elements without exposing internals
Mediator Central coordinator Reduce N:M coupling between collaborating objects
Memento State snapshot Save/restore state without breaking encapsulation
Observer Publish-subscribe Notify dependents of state changes automatically
State State-driven delegation Change object behavior when state changes
Strategy Algorithm delegation Swap algorithms at runtime
Template Method Skeleton + hooks Reuse algorithm structure, vary individual steps
Visitor Double dispatch Add operations to class hierarchies without modifying them

Decision Guide

How do objects communicate or behave?
│
├─ Pass request along until someone handles it?
│  └──▶ Chain of Responsibility
│
├─ Encapsulate a request for undo/redo/queuing?
│  └──▶ Command
│
├─ Traverse a collection without exposing internals?
│  └──▶ Iterator
│
├─ Reduce coupling between many interacting objects?
│  └──▶ Mediator
│
├─ Save and restore object state?
│  └──▶ Memento
│
├─ Notify others when state changes?
│  └──▶ Observer
│
├─ Change behavior based on internal state?
│  └──▶ State
│
├─ Swap algorithms at runtime?
│  └──▶ Strategy
│
├─ Reuse algorithm skeleton, vary steps?
│  └──▶ Template Method
│
├─ Add operations across a class hierarchy?
│  └──▶ Visitor
│
└─ Evaluate simple grammar or expressions?
   └──▶ Interpreter (but consider a parser library first)
Install via CLI
npx skills add https://github.com/Tyler-R-Kendrick/agent-skills --skill behavioral
Repository Details
star Stars 10
call_split Forks 3
navigation Branch main
article Path SKILL.md
More from Creator
Tyler-R-Kendrick
Tyler-R-Kendrick Explore all skills →