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 viasave()and restores viarestore(). - 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.
Related Patterns
Command
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Iterator
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Prototype
Create objects by cloning a prototype rather than constructing from scratch.