GoF Design Patterns: Behavioral Patterns (3) - Chain of Responsibility, Memento | TypeScript Examples

Design PatternsGoFBehavioral PatternsChain of Responsibility PatternMemento PatternObject-OrientedTypeScript
Read in about 6 min read
Published: 2025-07-05
Last modified: 2025-07-05
View count: 45

Summary

This is the third part of the Behavioral Patterns series in GoF Design Patterns. Learn the concepts and TypeScript examples of the Chain of Responsibility pattern, which passes a request along a chain of objects, and the Memento pattern, which saves and restores an object's state while preserving encapsulation.

In the second part on Behavioral Patterns, we explored the Command Pattern and the Template Method Pattern. Having learned how to encapsulate requests as objects and define the skeleton of an algorithm, we will now explore patterns that give multiple objects a chance to handle a request or safely save an object's state.

This post covers the following patterns:

  • ⛓️ Chain of Responsibility Pattern: Decouples the sender of a request from its receiver and links potential receivers together like a chain.
  • 💾 Memento Pattern: Allows you to save and restore the previous state of an object without revealing the details of its implementation.

Let's see how these patterns reduce coupling and increase flexibility in a system!

⛓️ 5. Chain of Responsibility Pattern

The Chain of Responsibility pattern is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain. The client that sends the request only needs to know about the first handler in the chain, not which handler will actually process it.

This pattern can be used in two main ways:

  1. Exclusive Handling: As the request travels along the chain, one handler processes it and the process stops. (e.g., an approval system)
  2. Inclusive Handling: Multiple handlers process a single request sequentially. (e.g., web framework middleware)

🏗️ Basic Structure

  • 📜 Handler: An interface that all concrete handlers must implement. It defines a setNext() method to link to the next handler and a handle() method to process the request.
  • ConcreteHandler: A concrete implementation of the Handler interface. Inside its handle() method, it performs its own task and decides whether to pass the request to the next handler.
  • Client: Creates the request and passes it to the first Handler in the chain.

💻 Example 1: Customer Support System (Exclusive Handling)

This example shows the "exclusive handling" approach, where one handler processes the request and the chain is terminated.

typescript
// Handler: The common interface for all handlers
abstract class SupportHandler {
  protected nextHandler: SupportHandler | null = null;

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

  handle(query: { type: string; message: string }): void {
    if (this.nextHandler) {
      this.nextHandler.handle(query);
    } else {
      console.log("This query cannot be handled by anyone.");
    }
  }
}

// ConcreteHandler: Tech Support Team
class TechSupportHandler extends SupportHandler {
  handle(query: { type: string; message: string }): void {
    if (query.type === "tech") {
      console.log(`[Tech Support] Handling query: "${query.message}"`);
    } else {
      super.handle(query); // Pass to the next if can't handle
    }
  }
}
// ... other handlers (Billing, General) ...

In the client code, when handlers are linked and a request is sent, it travels down the chain until it finds the single handler capable of processing it.

💻 Example 2: Web Server Middleware (Inclusive Handling)

This approach allows a single request to be processed by multiple handlers in sequence. Each handler performs its task and then always passes the request to the next handler to accumulate operations.

typescript
// The request object to be processed
interface HttpRequest {
  path: string;
  user?: { id: number; name: string }; // A property to be filled by the auth middleware
}

// Abstract Handler class
abstract class Middleware {
  protected next: Middleware | null = null;

  setNext(middleware: Middleware): Middleware {
    this.next = middleware;
    return middleware;
  }

  // handle now always calls the next in the chain
  handle(request: HttpRequest): void {
    if (this.next) {
      this.next.handle(request);
    }
  }
}

// ConcreteHandler 1: Authentication Middleware
class AuthMiddleware extends Middleware {
  handle(request: HttpRequest): void {
    console.log("1. [Auth] User token checked. Adding user info to request.");
    request.user = { id: 123, name: "Jaahyun Kim" };
    super.handle(request); // Pass to the next
  }
}

// ConcreteHandler 2: Logging Middleware
class LoggingMiddleware extends Middleware {
  handle(request: HttpRequest): void {
    console.log(
      `2. [Logging] Path: ${request.path}, User: ${request.user?.name}`
    );
    super.handle(request); // Pass to the next
  }
}

// Final Handler
class AppController extends Middleware {
  handle(request: HttpRequest): void {
    console.log("3. [Controller] Executing final business logic.");
    // End of the chain, so super.handle() is not called
  }
}

// Client Code
const auth = new AuthMiddleware();
const logging = new LoggingMiddleware();
const controller = new AppController();

// Link the chain: Auth -> Logging -> Controller
auth.setNext(logging).setNext(controller);

const request: HttpRequest = { path: "/profile" };
console.log("--- Client request received ---");
auth.handle(request);

This way, a single request can be processed sequentially by multiple handlers for tasks like authentication, logging, etc.

Key Takeaways for Chain of Responsibility Pattern 🔑

  • ⛓️ Decouples the sender from the receiver and links receivers together like a chain.
  • 🤝 Reduces coupling: The sender doesn't need to know which receiver will handle the request.
  • ➡️ A single request can be processed by multiple objects (middleware style).
  • 💡 It's flexible to add new handlers or change their order.

💾 6. Memento Pattern

The Memento pattern is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation. The most important goal of this pattern is to preserve encapsulation.

A perfect analogy is the 'Save Game' 🎮 feature. A player can save the current state of the game (the protagonist's location, level, items, etc.) and later load it to resume from that point. The game's complex internal data structure is not directly exposed to the outside world; instead, the state is safely saved and restored through an opaque object called a 'save file' (the memento).

🏗️ Basic Structure

  • 🎬 Originator: The original object whose state needs to be saved and restored. It has a save() method to create a Memento object containing its current state, and a restore() method to restore its state from a Memento object.
  • 💾 Memento: An object that stores the internal state of the Originator. Only the Originator should have full access to the memento's data. Other objects, especially the Caretaker, cannot inspect the memento's content.
  • 📚 Caretaker: An object that is responsible for keeping the Memento. It doesn't inspect or modify the memento's content. It just receives a memento from the Originator to store it and later passes it back to the Originator. (e.g., a History object that manages undo operations).

💻 Example: Creating a Simple Text Editor

Let's create a simple text editor that allows writing text, saving the content at a certain point (snapshot), and reverting to it (undo).

First, let's define the Memento class that will store the editor's state.

typescript
// Memento: Stores the state (content) of the editor
class EditorMemento {
  // Using readonly to make it immutable from the outside
  constructor(public readonly content: string) {}
}

Next, let's create the Originator, the Editor class, which is the object whose state we want to save and restore.

typescript
// Originator: The original object
class Editor {
  private content: string = "";

  write(text: string): void {
    this.content += text;
  }

  getContent(): string {
    return this.content;
  }

  // Saves the current state to a Memento
  save(): EditorMemento {
    console.log("Saving state: ", this.content);
    return new EditorMemento(this.content);
  }

  // Restores the state from a Memento
  restore(memento: EditorMemento): void {
    this.content = memento.content;
    console.log("Restoring state: ", this.content);
  }
}

Finally, let's create the Caretaker, the History class, which will manage the mementos.

typescript
// Caretaker: Keeps the mementos but doesn't know their content
class History {
  private mementos: EditorMemento[] = [];
  private editor: Editor;

  constructor(editor: Editor) {
    this.editor = editor;
  }

  push(): void {
    this.mementos.push(this.editor.save());
  }

  undo(): void {
    // Pop the last state (current state) to discard it
    this.mementos.pop();

    // Get the previous state and restore
    const lastMemento = this.mementos[this.mementos.length - 1];
    if (lastMemento) {
      this.editor.restore(lastMemento);
    } else {
      // If the stack is empty, restore to the initial state
      this.editor.restore(new EditorMemento(""));
    }
  }
}

// Client Code
const editor = new Editor();
const history = new History(editor);

// 1. Write the first sentence and save
editor.write("Hello. ");
history.push(); // Save state: "Hello. "

// 2. Write the second sentence and save
editor.write("This is the Memento pattern.");
history.push(); // Save state: "Hello. This is the Memento pattern."

// 3. Check the current content
console.log("Current content: ", editor.getContent());
// Output: Current content:  Hello. This is the Memento pattern.

// 4. Undo
history.undo();
console.log("Content after undo: ", editor.getContent());
// Output:
// Restoring state:  Hello.
// Content after undo:  Hello.

// 5. Undo one more time
history.undo();
console.log("Content after second undo: ", editor.getContent());
// Output:
// Restoring state:
// Content after second undo:

The History object doesn't even know that EditorMemento has a content property. It faithfully performs its role of pushing and popping memento objects onto a stack. This allows powerful state management features like undo to be implemented while the Editor's internal state (content) remains safely protected from the outside world (encapsulation).

Key Takeaways for Memento Pattern 🔑

  • 💾 Saves an object's internal state to the outside without violating encapsulation.
  • 🎬 Snapshot and Restore: Allows you to save the state at a specific point in time and later revert to that state.
  • ↩️ Very useful for implementing Undo/Redo, transactions, and save/load game features.
  • Role Separation: Roles are clearly divided into the Originator who creates the state, the Memento who stores the state, and the Caretaker who manages the state.

Over these three parts, we have covered six key behavioral patterns. These patterns provide deep insights into how objects should collaborate and divide responsibilities. In the next series, we will explore other useful patterns that we haven't covered yet!