Skip to content

Memento Pattern

Overview

The Memento pattern is a behavioral design pattern that provides the ability to restore an object to its previous state without revealing the details of its implementation. It's also known as the Snapshot pattern, as it captures and externalizes an object's internal state so that the object can be restored to this state later.

Intent

  • Capture and externalize an object's internal state without violating encapsulation
  • Allow the object to be restored to this state later
  • Provide undo/redo functionality
  • Create snapshots of object state

Problem

Imagine you're building a text editor application. Users expect to be able to undo their actions - delete some text, move it back. But how do you implement this without exposing the editor's internal state?

Direct approach problems: - Violates encapsulation: Making state public breaks object boundaries - Tight coupling: Undo mechanism tied to specific object structure - Fragile: Changes to object structure break undo functionality - Complex: Object must manage its own history

You need a way to save and restore object state while: - Preserving encapsulation - Keeping the originating object simple - Supporting multiple snapshots - Allowing flexible restoration

Solution

The Memento pattern delegates creating the state snapshot to the actual owner of that state, the originator object. Instead of other objects trying to copy the editor's state from the "outside," the editor class itself can make the snapshot since it has full access to its own state.

The pattern suggests storing the copy of the object's state in a special object called memento. The contents of the memento aren't accessible to any other object except the one that produced it. Other objects must communicate with mementos using a limited interface which may allow fetching the snapshot's metadata but not the original object's state.

Structure

Memento Pattern Diagram 0

Components

  1. Originator: Creates a memento containing a snapshot of its current internal state and can restore its state from a memento.

  2. Memento: Stores internal state of the Originator object. The memento may store as much or as little of the originator's internal state as necessary.

  3. Caretaker: Responsible for the memento's safekeeping. Never operates on or examines the contents of a memento.

Implementation Details

Memento Class

class Memento {
private:
    std::string state_;
    std::string timestamp_;

public:
    Memento(std::string state, std::string timestamp)
        : state_(std::move(state))
        , timestamp_(std::move(timestamp)) {}

    const std::string& getState() const { return state_; }
    const std::string& getTimestamp() const { return timestamp_; }
};

Originator Class

class Originator {
private:
    std::string state_;

public:
    void doSomething() {
        // Change state
        state_ = "New State";
    }

    std::shared_ptr<Memento> save() const {
        return std::make_shared<Memento>(
            state_, 
            getCurrentTimestamp()
        );
    }

    void restore(std::shared_ptr<Memento> memento) {
        state_ = memento->getState();
    }
};

Caretaker Class

class Caretaker {
private:
    std::vector<std::shared_ptr<Memento>> mementos_;
    Originator* originator_;

public:
    void backup() {
        mementos_.push_back(originator_->save());
    }

    void undo() {
        if (mementos_.empty()) return;

        auto memento = mementos_.back();
        mementos_.pop_back();
        originator_->restore(memento);
    }
};

Real-World Example 1: Text Editor with Undo/Redo

Memento Pattern Diagram 1

Implementation

class TextEditor {
private:
    std::string content_;
    int cursorPosition_;

public:
    void type(const std::string& text) {
        content_.insert(cursorPosition_, text);
        cursorPosition_ += text.length();
    }

    void deleteText(int length) {
        if (cursorPosition_ >= length) {
            content_.erase(cursorPosition_ - length, length);
            cursorPosition_ -= length;
        }
    }

    std::shared_ptr<EditorMemento> save() const {
        return std::make_shared<EditorMemento>(
            content_, 
            cursorPosition_,
            getCurrentTimestamp()
        );
    }

    void restore(std::shared_ptr<EditorMemento> memento) {
        content_ = memento->getContent();
        cursorPosition_ = memento->getCursorPosition();
    }
};

class EditorHistory {
private:
    std::vector<std::shared_ptr<EditorMemento>> history_;
    int currentIndex_;
    TextEditor* editor_;

public:
    void save() {
        // Remove future states when saving after undo
        if (currentIndex_ < history_.size() - 1) {
            history_.erase(
                history_.begin() + currentIndex_ + 1, 
                history_.end()
            );
        }
        history_.push_back(editor_->save());
        currentIndex_++;
    }

    void undo() {
        if (currentIndex_ > 0) {
            currentIndex_--;
            editor_->restore(history_[currentIndex_]);
        }
    }

    void redo() {
        if (currentIndex_ < history_.size() - 1) {
            currentIndex_++;
            editor_->restore(history_[currentIndex_]);
        }
    }
};

// Usage
TextEditor editor;
EditorHistory history(&editor);

history.save();           // Save initial state
editor.type("Hello");
history.save();
editor.type(" World");
history.save();

history.undo();          // Back to "Hello"
history.undo();          // Back to ""
history.redo();          // Forward to "Hello"

Real-World Example 2: Game Save System

Memento Pattern Diagram 2

Implementation

class Game {
private:
    int level_;
    int score_;
    int health_;
    int x_, y_;

public:
    void play() {
        level_++;
        score_ += 100;
        // Update game state
    }

    void takeDamage(int damage) {
        health_ -= damage;
        if (health_ < 0) health_ = 0;
    }

    std::shared_ptr<GameStateMemento> save() const {
        return std::make_shared<GameStateMemento>(
            level_, score_, health_, x_, y_,
            getCurrentTimestamp()
        );
    }

    void restore(std::shared_ptr<GameStateMemento> memento) {
        level_ = memento->getLevel();
        score_ = memento->getScore();
        health_ = memento->getHealth();
        // Restore position, etc.
    }
};

class GameSaveManager {
private:
    std::vector<std::shared_ptr<GameStateMemento>> saves_;
    Game* game_;

public:
    void quickSave() {
        saves_.push_back(game_->save());
    }

    void quickLoad() {
        if (!saves_.empty()) {
            game_->restore(saves_.back());
        }
    }

    void loadSave(size_t index) {
        if (index < saves_.size()) {
            game_->restore(saves_[index]);
        }
    }
};

// Usage
Game game;
GameSaveManager saveManager(&game);

saveManager.quickSave();    // Checkpoint 1
game.play();
game.takeDamage(30);

saveManager.quickSave();    // Checkpoint 2
game.play();
game.takeDamage(60);        // Died!

saveManager.quickLoad();    // Restore to checkpoint 2
saveManager.loadSave(0);    // Or restore to checkpoint 1

Applicability

Use the Memento pattern when:

  1. Undo/Redo Functionality: You need to implement undo or rollback operations.

  2. Snapshot Capability: You want to produce snapshots of an object's state to be able to restore it later.

  3. Encapsulation Protection: Direct access to the object's fields/getters/setters violates its encapsulation.

  4. State History: You need to maintain a history of an object's states.

  5. Checkpoints: You want to create checkpoints in a process that can be returned to.

Advantages

  1. Preserves Encapsulation: Doesn't violate encapsulation boundaries of the originator.

  2. Simplified Originator: The originator doesn't need to manage different versions of its state.

  3. Undo Support: Easy implementation of undo/redo functionality.

  4. State History: Can maintain complete history of state changes.

  5. Rollback Capability: Easy to rollback to any previous state.

  6. Flexible Snapshots: Can capture partial or complete state as needed.

Disadvantages

  1. Memory Overhead: Storing multiple mementos can consume significant memory if the originator's state is large.

  2. Performance Cost: Creating and restoring mementos takes time, especially for large states.

  3. Lifecycle Management: The caretaker must know when to delete old mementos to prevent memory issues.

  4. Copying Cost: Full state copies can be expensive for complex objects.

  5. Language Limitations: Some languages don't provide adequate support for encapsulation (access modifiers).

Relations with Other Patterns

  • Command: Commands can use Memento to maintain state for undo operations. Command stores the memento before executing and can restore it during undo.
  • Iterator: Can use Memento to capture and restore iteration state.
  • Prototype: Memento is simpler than Prototype when you only need to restore state and not create new objects. Prototype creates a copy of the entire object, while Memento captures specific state.
  • Serialization: Memento can be combined with serialization for persistent storage of states.

Design Variations

1. Incremental Memento

For large states, store only changes instead of complete snapshots:

class IncrementalMemento {
private:
    std::map<std::string, std::string> changes_;

public:
    void recordChange(const std::string& field, 
                     const std::string& value) {
        changes_[field] = value;
    }
};

2. Serializable Memento

For persistent storage:

class SerializableMemento {
public:
    std::string serialize() const {
        // Convert state to JSON/XML/binary
        return json_data;
    }

    static Memento deserialize(const std::string& data) {
        // Restore from JSON/XML/binary
        return memento;
    }
};

3. Memento with Metadata

Include additional information:

class EnhancedMemento {
private:
    std::string state_;
    std::string description_;
    std::string timestamp_;
    std::string username_;

public:
    std::string getDescription() const { 
        return description_; 
    }
};

Best Practices

  1. Limit History Size: Implement a maximum history size to prevent memory issues.
class Caretaker {
private:
    static const size_t MAX_HISTORY = 100;

    void backup() {
        if (mementos_.size() >= MAX_HISTORY) {
            mementos_.erase(mementos_.begin());
        }
        mementos_.push_back(originator_->save());
    }
};
  1. Use Smart Pointers: Automatic memory management for mementos.
std::vector<std::shared_ptr<Memento>> mementos_;
  1. Implement Copy-on-Write: For efficiency with large states.

  2. Consider Compression: Compress mementos for large states.

  3. Lazy Restoration: Restore state only when needed.

  4. Immutable Mementos: Make mementos immutable after creation.

  5. Timestamp Mementos: Add timestamps for debugging and display.

Performance Considerations

Memory Optimization

1
2
3
4
5
6
7
// Instead of storing complete state
class OptimizedMemento {
private:
    // Store only changed fields
    std::shared_ptr<State> baseState_;
    std::map<std::string, Value> changes_;
};

Lazy Copying

class LazyMemento {
private:
    std::shared_ptr<State> sharedState_;

    // Copy only when modified
    void ensureUnique() {
        if (!sharedState_.unique()) {
            sharedState_ = std::make_shared<State>(*sharedState_);
        }
    }
};

Real-World Applications

1. Text Editors

  • Undo/redo functionality
  • Document revision history
  • Auto-save and crash recovery

2. Games

  • Save/load game state
  • Checkpoint systems
  • Replay functionality
  • Network synchronization (rollback)

3. Databases

  • Transaction rollback
  • Savepoints
  • Point-in-time recovery

4. Graphics Editors

  • Layer state management
  • Non-destructive editing
  • History palette

5. Configuration Management

  • Settings backup
  • Configuration versioning
  • Rollback to previous settings

6. Version Control Systems

  • Commit snapshots (Git)
  • Branch management
  • Revert operations

7. Workflow Engines

  • Process state checkpoints
  • Resume from failure
  • Audit trail

Example: Database Transaction

class Database {
private:
    std::map<std::string, std::string> data_;

public:
    class TransactionMemento {
    private:
        std::map<std::string, std::string> snapshot_;
    public:
        TransactionMemento(const std::map<std::string, std::string>& data)
            : snapshot_(data) {}

        const std::map<std::string, std::string>& getSnapshot() const {
            return snapshot_;
        }
    };

    std::shared_ptr<TransactionMemento> beginTransaction() {
        return std::make_shared<TransactionMemento>(data_);
    }

    void rollback(std::shared_ptr<TransactionMemento> memento) {
        data_ = memento->getSnapshot();
    }

    void commit() {
        // Transaction complete, no rollback needed
    }
};

// Usage
Database db;
auto transaction = db.beginTransaction();

try {
    // Perform operations
    db.insert("key", "value");
    db.update("key2", "value2");
    db.commit();
} catch (...) {
    db.rollback(transaction);  // Restore to transaction start
}

Comparison: Memento vs Command

Aspect Memento Command
Purpose Save/restore state Encapsulate operations
Undo Method Restore snapshot Execute inverse operation
Memory Stores complete state Stores operation parameters
Performance Can be memory-intensive More memory-efficient
Complexity Simpler implementation More complex logic
Use Case Large state, simple undo Small state, complex operations

Conclusion

The Memento pattern is essential for implementing undo/redo functionality and state management. It:

  • Preserves encapsulation while enabling state restoration
  • Simplifies originator by delegating state management
  • Enables time travel through object state history
  • Supports checkpoints and rollback operations

Key considerations: - Memory usage for storing mementos - Performance cost of state copying - Lifecycle management of mementos - Balance between functionality and resource usage

When properly implemented, the Memento pattern provides a clean, encapsulated way to manage object state history, making it invaluable for applications requiring undo functionality, checkpoints, or state rollback capabilities.