Skip to content

Design Patterns: Memento

Published: at 12:18 PM

The Memento Design Pattern is a powerful tool for implementing undo and redo functionality or otherwise managing the state of objects in a way that adheres to software design principles.

What is the Memento Pattern?

The Memento Pattern allows an object to save its internal state so that it can be restored to that state later. Often referred to as the “Token Pattern,” you can think of a memento as a save point in a video game or a particular commit in source code management.

Real-World Applications of the Memento Pattern

Here are some common scenarios where the Memento Pattern is applied:

The Problem the Memento Pattern Solves

Any situation where you need the ability to roll back an object to its previous state can benefit from the Memento Pattern. If your application requires undo functionality, the Memento Pattern is likely a good fit, as it adds this capability without overburdening existing objects. This helps maintain adherence to the Single Responsibility Principle.

For primitive types like strings, where undoing changes might be straightforward, the Memento Pattern might be overkill. However, for complex objects, attempting to roll back a state without this pattern would require exposing the internal state to external components—violating encapsulation. The Memento Pattern allows you to capture and restore the internal state of an object without breaking encapsulation or compromising the Single Responsibility Principle.

Structure of the Memento Pattern

The Memento Pattern is composed of three main parts:

1. The Originator

The object whose state is being tracked (e.g., a document, game, etc.).

2. The Caretaker

The external object that interacts with the Originator (e.g., a UI, console app, etc.). The Caretaker manages the state of the Originator by performing operations and storing its states.

3. The Memento

The Memento is the key component of this design pattern. It captures and stores the internal state of the Originator. This state is then used to restore the Originator to its previous condition.

The Memento must hold the complete state of the Originator so that the Originator can be fully restored to a previous state using it.

UML Class Diagram

Originator
-state
+SetMemento(Memento m)
+CreateMemento()
Memento
-state
+GetState()
+SetState()

In this diagram:

The Originator directly interacts with the Memento, creating it, setting its internal state, and reading that state back as needed. Access to the Memento’s state should ideally be restricted to the Originator to maintain encapsulation.

The Caretaker is responsible for saving the Memento, either in memory or a persistent store if serialization is supported. Any user interaction for selecting or organizing Mementos is handled by the Caretaker.

How the Memento Pattern Works

When a user requests to save the state of the system:

  1. The Caretaker calls the Originator.
  2. The Originator creates a new Memento and sets its state.

Later, when the user requests to restore the system’s state:

  1. The Caretaker calls the Originator’s SetMemento() method, passing in the desired Memento.
  2. The Originator retrieves the state from the Memento and updates its current state accordingly.

aMementoanOriginatoraCareTakeraMementoanOriginatoraCareTaker1. Create Memento()2. new Memento3. SetState()4. SetMemento(aMemento)

Key Points to Remember

Managing Mementos in the Caretaker

A straightforward way to manage Mementos in the Caretaker is by using a stack.

For example, say:

To restore the system to a previous state, simply pop the last state from the stack (State 3) and make it the current state of the system. In applications that support only undo, there is no way to save State 4; it is replaced by State 3.

Supporting Redo Operations

Supporting redo is easy once you have undo set up. Instead of deleting the current state when undoing, save that state to a redo stack.

For example:

To redo:

Implementing Undo and Redo Operations

When implementing these operations, ensure the Caretaker does the following:

Remember, Mementos should be immutable objects that encapsulate state without any behavior.

Steps to Apply the Memento Pattern

  1. Follow refactoring fundamentals to ensure you don’t introduce bugs.
  2. Regardless of whether you have a state management system built in, define a Memento type. Keep it simple and ensure its accessors are only available to the Originator to preserve encapsulation.
  3. Add methods to the Originator to save and restore state.
  4. Create the Caretaker, which will manage the Mementos. Identify the class responsible for this and create any necessary structures to store one or more Mementos.

Alternative Approaches

Reverse Operations

Instead of storing the entire state, you can store the operations and their corresponding reverse operations. This is often used in conjunction with the Command Pattern.

For example, in a calculator:

  1. Start with 30.
  2. Add 5 to get 35.
  3. Multiply by 2 to get 70.

To undo:

This approach works well when operations have clear, consistent reversals. However, it can break down when reversals are ambiguous, as in squaring operations.

Storing Diffs

Another approach is to store only the differences (diffs) between states rather than entire states, similar to how version control systems like Git work.

This approach works well for small changes and short history spans. However, it can be resource-intensive if generating diffs or applying multiple diffs in succession requires more computational resources than simply copying the entire state.

Key Takeaways