110
behavioral

Memento

Reference Wikipedia ↗
Memento — class diagram
Plate 110 class diagram

The Memento pattern allows saving and restoring an object’s internal state without violating encapsulation. It achieves this by creating a separate “memento” object that holds the state, which the originator can create and hand to a caretaker for storage. The caretaker is responsible for holding mementos but cannot inspect or alter their contents.

This pattern is particularly useful in scenarios requiring undo/redo functionality, transaction management, or the ability to revert to previous states without exposing the object’s internal details. It supports the principle of information hiding by ensuring the object’s internal representation remains private.

Usage

The Memento pattern finds widespread use in applications where maintaining historical states is crucial. Common scenarios include:

  • Undo/Redo Systems: Text editors, image manipulation software, and game engines all utilize mementos to implement undo and redo features.
  • Transaction Management: Database systems and financial applications use mementos to save the state before a transaction and roll back to that state if the transaction fails.
  • Checkpoints: Game saving mechanisms often employ mementos to store the game’s state at specific points, allowing players to resume from those checkpoints.
  • Version Control: While more complex, the underlying principle of storing previous states is similar to version control systems.

Examples

  • Git (Version Control): Git utilizes a more advanced version of the Memento pattern in its commits. Each commit essentially acts as a memento, storing a snapshot of the entire project’s state at a specific point in time. The caretaker is Git itself, managed by the user. While not a direct one-to-one Memento implementation because of merging and branching, the core idea of saving and restoring states remains.

  • Java Serialized Object Stream: Java’s Serializable interface and ObjectOutputStream/ObjectInputStream classes provide a built-in mechanism for serializing and deserializing objects. Serializing an object can be viewed as creating a memento (a byte stream representing the object’s state), and deserializing restores the object to that prior state. The ObjectOutputStream plays the role of the caretaker, storing the serialized data. This is a simple automatic implementation of the pattern.

Specimens

15 implementations
Specimen 110.01 Dart View specimen ↗

The Memento pattern captures and externalizes an object’s internal state so that the object can be restored to this state later, even if the object is modified or destroyed. It consists of three parts: the Originator, the Memento, and the Caretaker. The Originator holds the state, the Memento is an immutable snapshot of that state, and the Caretaker is responsible for storing and retrieving Mementos without directly accessing the Originator’s state.

This Dart implementation uses classes to represent each part. The Editor is the Originator, holding the text and providing methods to set and save its state. The TextSnapshot is the Memento, storing the text immutably. The History class acts as the Caretaker, managing a list of TextSnapshot objects. Dart’s immutability features (using final) are leveraged in the TextSnapshot to ensure the state remains consistent. This approach aligns with Dart’s object-oriented nature and promotes encapsulation.

class TextSnapshot {
  final String text;

  TextSnapshot(this.text);

  String getText() => text;
}

class Editor {
  String _text = "";

  String getText() => _text;
  void setText(String text) {
    _text = text;
  }

  TextSnapshot save() {
    return TextSnapshot(_text);
  }

  void restore(TextSnapshot snapshot) {
    _text = snapshot.getText();
  }
}

class History {
  List<TextSnapshot> _snapshots = [];

  void push(TextSnapshot snapshot) {
    _snapshots.add(snapshot);
  }

  TextSnapshot pop() {
    if (_snapshots.isEmpty) {
      return null;
    }
    return _snapshots.removeLast();
  }
}

void main() {
  final editor = Editor();
  final history = History();

  editor.setText("Hello");
  history.push(editor.save());

  editor.setText("World");
  history.push(editor.save());

  print("Current text: ${editor.getText()}"); // Output: Current text: World

  final snapshot = history.pop();
  editor.restore(snapshot!);
  print("Restored text: ${editor.getText()}"); // Output: Restored text: Hello

  final snapshot2 = history.pop();
  editor.restore(snapshot2!);
  print("Restored text: ${editor.getText()}"); // Output: Restored text: 
}