Memento Pattern

The Memento Pattern is a behavioral design pattern that allows an object to capture and store its current state so it can be restored later without violating encapsulation. This pattern is particularly useful when implementing features like undo/redo operations, where the state of an object needs to be preserved and restored at various points in time.

Key Components

  1. Originator: The object whose state needs to be saved and restored. It creates a Memento to capture its state and can restore its state from a Memento.
  2. Memento: A class that holds the state of the Originator at a specific point in time. It provides methods to access the stored state, typically restricting access to the Originator.
  3. Caretaker: The object responsible for storing and managing Mementos. It keeps track of Mementos (e.g., in a list for undo/redo) but does not modify or inspect their contents.
  4. Client: Initiates the saving and restoring of the Originator’s state by interacting with the Caretaker and Originator.

How It Works

  • The Originator creates a Memento containing a snapshot of its current state when requested.
  • The Caretaker stores the Memento, often in a stack or list, to support operations like undo or redo.
  • To restore a previous state, the Caretaker passes a Memento back to the Originator, which uses it to revert to the saved state.
  • The Memento typically restricts access to its state (e.g., using private fields or restricted interfaces) to maintain encapsulation, allowing only the Originator to read or modify it.

Sample Implementation

A text editor where the content (state) can be saved and restored to support undo functionality.

// Memento
class EditorMemento {
    private final String content; // Immutable state

    public EditorMemento(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

// Originator
class TextEditor {
    private String content;

    public TextEditor() {
        this.content = "";
    }

    public void write(String text) {
        content += text;
        System.out.println("Current content: " + content);
    }

    public EditorMemento save() {
        return new EditorMemento(content);
    }

    public void restore(EditorMemento memento) {
        content = memento.getContent();
        System.out.println("Restored content: " + content);
    }

    public String getContent() {
        return content;
    }
}

// Caretaker
class History {
    private List<EditorMemento> mementos;

    public History() {
        mementos = new ArrayList<>();
    }

    public void save(EditorMemento memento) {
        mementos.add(memento);
    }

    public EditorMemento undo() {
        if (mementos.isEmpty()) {
            return null;
        }
        EditorMemento memento = mementos.remove(mementos.size() - 1);
        return memento;
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        // Create Originator and Caretaker
        TextEditor editor = new TextEditor();
        History history = new History();

        // Edit and save states
        editor.write("Hello");
        history.save(editor.save());

        editor.write(", World!");
        history.save(editor.save());

        editor.write(" How are you?");
        history.save(editor.save());

        // Undo operations
        System.out.println("\nUndoing changes:");
        EditorMemento memento = history.undo();
        if (memento != null) {
            editor.restore(memento);
        }

        memento = history.undo();
        if (memento != null) {
            editor.restore(memento);
        }

        memento = history.undo();
        if (memento != null) {
            editor.restore(memento);
        }

        // Try to undo when history is empty
        memento = history.undo();
        if (memento == null) {
            System.out.println("No more states to undo.");
        }
    }
}
/*
Current content: Hello
Current content: Hello, World!
Current content: Hello, World! How are you?

Undoing changes:
Restored content: Hello, World!
Restored content: Hello
Restored content: 
No more states to undo.
*/Code language: PHP (php)

Sample Implementation

Implementing undo functionality for transactions or state changes in accounts.

import java.util.Stack;
//MementoDemo.java
// Memento Class (AccountMemento): This class represents the state of the Account that needs to be saved and restored.
class AccountMemento {
    private final String owner;
    private final double balance;

    public AccountMemento(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    public String getOwner() {
        return owner;
    }

    public double getBalance() {
        return balance;
    }
}

// Originator Class (Account): This class represents the Account whose state will change and for which undo functionality is needed.
class Account {
    private String owner;
    private double balance;

    public Account(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    // Method to save current state as a memento
    public AccountMemento saveToMemento() {
        return new AccountMemento(owner, balance);
    }

    // Method to restore state from a memento
    public void restoreFromMemento(AccountMemento memento) {
        this.owner = memento.getOwner();
        this.balance = memento.getBalance();
    }

    // Method to perform a transaction (state change)
    public void performTransaction(double amount) {
        if (balance + amount >= 0) {
            balance += amount;
            System.out.println("Transaction successful. New balance: " + balance);
        } else {
            System.out.println("Insufficient funds for this transaction.");
        }
    }

    public String getOwner() {
        return owner;
    }

    public double getBalance() {
        return balance;
    }
}

// CareTaker Class (AccountHistory): This class manages the history of Account states and supports undo operations.
class AccountHistory {
    private final Stack<AccountMemento> history = new Stack<>();

    // Method to save state memento
    public void saveState(AccountMemento memento) {
        history.push(memento);
    }

    // Method to undo last transaction
    public void undoLastTransaction(Account account) {
        if (!history.isEmpty()) {
            AccountMemento memento = history.pop();
            account.restoreFromMemento(memento);
            System.out.println("Undo successful. Current balance: " + account.getBalance());
        } else {
            System.out.println("Nothing to undo.");
        }
    }
}

// Demo Application: Test the undo functionality.
public class MementoDemo {
    public static void main(String[] args) {
        // Create an account
        Account account = new Account("John Doe", 1000.0);

        // Create history to keep track of mementos
        AccountHistory history = new AccountHistory();

        // Perform transactions
        account.performTransaction(-500.0); // Withdraw 500
        history.saveState(account.saveToMemento()); // Save state after transaction

        account.performTransaction(300.0); // Deposit 300
        history.saveState(account.saveToMemento()); // Save state after transaction

        account.performTransaction(-200.0); // Withdraw 200
        history.saveState(account.saveToMemento()); // Save state after transaction

        // Current state
        System.out.println("Current Balance: " + account.getBalance());

        // Undo last transaction
        history.undoLastTransaction(account);
        System.out.println("Current Balance after Undo: " + account.getBalance());

        // Undo again
        history.undoLastTransaction(account);
        System.out.println("Current Balance after Undo: " + account.getBalance());
    }
}

/*
C:\>javac MementoDemo.java
C:\>java MementoDemo
Transaction successful. New balance: 500.0
Transaction successful. New balance: 800.0
Transaction successful. New balance: 600.0
Current Balance: 600.0
Undo successful. Current balance: 600.0
Current Balance after Undo: 600.0
Undo successful. Current balance: 800.0
Current Balance after Undo: 800.0
*/

Pros

  • Encapsulation: Preserves the Originator’s encapsulation by restricting access to its state.
  • Undo/Redo Support: Easily implements undo/redo by storing and retrieving Mementos.
  • Flexibility: Allows multiple states to be saved and restored as needed.
  • Separation of Concerns: The Caretaker handles state storage, while the Originator manages state creation and restoration.

Cons

  • Memory Overhead: Storing many Mementos, especially for large or complex states, can consume significant memory.
  • Performance Cost: Creating and restoring Mementos may be expensive for complex objects.
  • Complexity: Adds extra classes and management logic, increasing code complexity.
  • State Management: The Caretaker must carefully manage Mementos to avoid issues like memory leaks or invalid states.

When to Use

  • When you need to save and restore an object’s state without exposing its internal structure.
  • When implementing undo/redo functionality in an application.
  • When you want to capture snapshots of an object’s state for later restoration.
  • When you need to support checkpointing or rollback mechanisms.

Real-World Example

  • Text Editors: Saving document states for undo/redo (e.g., Microsoft Word’s undo feature).
  • Game Development: Saving game states (e.g., player position, score) for checkpoints or reloads.
  • Database Transactions: Storing states for rollback in case of transaction failure.
  • Configuration Managers: Saving application settings to revert to previous configurations.

The Memento Pattern is a behavioral design pattern that allows an object to save and restore its previous state without exposing its internal implementation. It provides a way to capture and externalize an object’s internal state so that it can be restored later, typically used to implement features like undo/redo, history tracking, or checkpointing.

This pattern promotes encapsulation, as the internal details of the object are hidden from the caretaker (the object managing the memento), and only the originator (the object whose state is saved) can access its internal data.

Scroll to Top