Behavioral Complexity: Medium

Memento Pattern

Capture and externalize an object's internal state so that the object can be restored to this state later.

The Problem

A shopping cart changes frequently as users add, remove, and modify items. Users expect undo capability. Storing cart state externally would break encapsulation — external code would need to know about the cart’s internal structure to save and restore it.

The Solution

The shopping cart (originator) creates opaque snapshot objects (mementos) of its own state. A caretaker service stores these snapshots in undo and redo stacks. The cart can restore itself from any memento without exposing its internals.

Structure

  • Originator (ShoppingCart) — Creates mementos via save() and restores via restore().
  • Memento (CartMemento) — Immutable snapshot with timestamp.
  • Caretaker (CartHistoryService) — Manages undo/redo stacks.

Implementation

The shopping cart supports adding items (with quantity merging for duplicates), undo, redo, and full history viewing. Each modification saves a memento before the change.

export interface CartItem {
  name: string;
  price: number;
  quantity: number;
}

export class CartMemento {
  private readonly items: CartItem[];
  private readonly timestamp: string;

  constructor(items: CartItem[]) {
    this.items = items.map((item) => ({ ...item }));
    this.timestamp = new Date().toISOString();
  }

  getItems(): CartItem[] {
    return this.items.map((item) => ({ ...item }));
  }

  getTimestamp(): string {
    return this.timestamp;
  }

  getTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }
}

NestJS Integration

The CartHistoryService is an @Injectable() singleton that persists undo/redo stacks across requests. The ShoppingCart is also a singleton provider, simulating a single-user cart for demonstration. In production, you would use request-scoped or session-scoped providers.

When to Use

  • You need to implement undo/redo functionality.
  • You want to save and restore object state without violating encapsulation.
  • You need snapshots for transaction-like operations (commit/rollback).

When NOT to Use

  • The object’s state is trivially simple — just store a copy directly.
  • Creating mementos is expensive due to large state — consider storing deltas instead.