Skip to main content

Memento Pattern

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

Problem

You need to implement undo/redo or snapshot functionality, but saving state directly exposes the internal details of an object, breaking encapsulation.

Common Scenarios:

  • Undo/redo in text editors or drawing applications
  • Game save states and checkpoints
  • Transaction rollback in databases
  • Wizard-style forms where users can go back
  • Version history for documents or configurations

Design Principles Applied

  1. Encapsulation - The Originator's internal state is never exposed to the Caretaker
  2. Single Responsibility - State saving is separated from the object that owns the state
  3. Open/Closed Principle - Add new state snapshots without modifying the Originator

Key Roles

RoleResponsibility
OriginatorThe object whose state is saved. Creates and restores mementos
MementoStores a snapshot of the Originator's state. Opaque to the Caretaker
CaretakerHolds mementos and decides when to save/restore. Never reads memento contents

UML Diagram

Implementation

Example 1: Text Editor with Undo

Step 1: Define the Memento

// Memento — stores a snapshot of the editor's text
// Only the Originator (Editor) can read its state
public class EditorMemento {
private final String text;
private final int cursorPosition;

public EditorMemento(String text, int cursorPosition) {
this.text = text;
this.cursorPosition = cursorPosition;
}

// Package-private so only classes in the same package (the Editor) can access state
String getText() { return text; }
int getCursorPosition() { return cursorPosition; }
}

Step 2: Define the Originator

// Originator — the text editor whose state we want to snapshot
public class TextEditor {
private String text = "";
private int cursorPosition = 0;

public void type(String words) {
text += words;
cursorPosition = text.length();
}

public void delete(int characters) {
if (characters > text.length()) characters = text.length();
text = text.substring(0, text.length() - characters);
cursorPosition = text.length();
}

// Creates a snapshot of current state
public EditorMemento save() {
return new EditorMemento(text, cursorPosition);
}

// Restores state from a snapshot
public void restore(EditorMemento memento) {
this.text = memento.getText();
this.cursorPosition = memento.getCursorPosition();
}

public String getText() { return text; }
}

Step 3: Define the Caretaker

// Caretaker — manages the history of snapshots
// It stores and retrieves mementos but never inspects their contents
public class EditorHistory {
private final Deque<EditorMemento> history = new ArrayDeque<>();

public void save(TextEditor editor) {
history.push(editor.save());
}

public void undo(TextEditor editor) {
if (!history.isEmpty()) {
editor.restore(history.pop());
}
}

public int size() { return history.size(); }
}

Step 4: Client Usage

TextEditor editor = new TextEditor();
EditorHistory history = new EditorHistory();

editor.type("Hello");
history.save(editor); // snapshot 1: "Hello"

editor.type(", World");
history.save(editor); // snapshot 2: "Hello, World"

editor.type("!!!");
System.out.println(editor.getText()); // Hello, World!!!

history.undo(editor);
System.out.println(editor.getText()); // Hello, World

history.undo(editor);
System.out.println(editor.getText()); // Hello

Example 2: Game Checkpoint

public class GameState {
private final int level;
private final int health;
private final int score;

public GameState(int level, int health, int score) {
this.level = level;
this.health = health;
this.score = score;
}

int getLevel() { return level; }
int getHealth() { return health; }
int getScore() { return score; }
}

public class Game {
private int level = 1;
private int health = 100;
private int score = 0;

public void progress(int levelsGained, int healthLost, int pointsScored) {
level += levelsGained;
health -= healthLost;
score += pointsScored;
}

public GameState save() {
return new GameState(level, health, score);
}

public void restore(GameState state) {
this.level = state.getLevel();
this.health = state.getHealth();
this.score = state.getScore();
}

@Override
public String toString() {
return "Level=" + level + ", Health=" + health + ", Score=" + score;
}
}

// Usage
Game game = new Game();
game.progress(2, 20, 300);
System.out.println(game); // Level=3, Health=80, Score=300

GameState checkpoint = game.save();

game.progress(1, 90, 100); // bad run, nearly died
System.out.println(game); // Level=4, Health=-10, Score=400

game.restore(checkpoint);
System.out.println(game); // Level=3, Health=80, Score=300

Real-World Examples

  • javax.swing.undo.UndoManager — Java Swing's built-in undo framework uses the Memento concept
  • Git commits — each commit is a snapshot (memento) of the repository state
  • Database transactions — a savepoint captures state that can be rolled back to
  • Serialization — serialising an object to bytes/JSON is a form of memento

Memento vs Similar Patterns

PatternIntent
MementoSnapshot and restore an object's private state without breaking encapsulation
CommandSupports undo by storing the inverse operation, not a full state snapshot
PrototypeClones an object's state, but the clone is a fully usable copy, not an opaque token
StateChanges an object's behaviour based on its current state; doesn't capture history