Memento Design Pattern – Implement Undo/Redo Functionality

The Memento design pattern is a powerful behavioral pattern that provides a way to capture and restore an object’s internal state. It allows objects to be saved and restored without violating encapsulation principles. In this blog post, we will explore the Memento design pattern in detail, discuss its benefits, and provide practical examples of its implementation.

Analogy

Let’s consider another real-world analogy for the Memento design pattern: a text editor with an undo/redo functionality.

Imagine you’re using a text editor to write a document. As you type and make changes, the text editor keeps track of the document’s content and allows you to undo or redo your actions.

In this analogy, there are 3 actors:

  • The Originator represents the text editor itself. It holds the current state of the document, including the text content.
  • The Memento represents a snapshot of the document’s state. It captures the text content at a specific point in time.
  • The Caretaker represents the undo/redo manager of the text editor. It stores and manages the Memento objects, allowing you to save states and navigate between them.

When you make changes to the document, such as typing, deleting, or formatting, the text editor (Originator) creates a new Memento object that captures the current text content. The Memento object is then added to the undo stack in the undo/redo manager (Caretaker).

If you decide to undo an action, the undo/redo manager retrieves the most recent Memento object from the undo stack and passes it back to the text editor. The text editor then restores its state by setting the text content to the value stored in the Memento.

Similarly, if you want to redo an action that was previously undone, the undo/redo manager retrieves the next Memento object from the redo stack and restores the text editor’s state using that Memento.

By utilizing the Memento design pattern in this scenario, the text editor can provide an undo/redo functionality without exposing the internal details of the document or compromising encapsulation. It enables users to navigate through different document states and easily revert or replay their actions, improving the overall editing experience.

Implementation

Components

As we discussed, The pattern consists of three main components: the Originator, the Memento, and the Caretaker.

  1. Originator: The Originator is the object whose state we want to capture and restore. It creates a Memento object containing a snapshot of its current state or can restore its state from a Memento object.
  2. Memento: The Memento is an object that stores the state of the Originator. It provides methods to retrieve the saved state and potentially modify it.
  3. Caretaker: The Caretaker is responsible for storing and managing the Memento objects. It interacts with the Originator to save and restore its state using Memento objects.

Steps

Continuing with our Text Editor example, we have following steps involved in implementation:

  1. The TextEditor class provides methods to set and retrieve the text content. It also has methods to create a Memento object (save()) and restore the state from a Memento object (restore()).
  2. The TextEditorMemento class stores the text content in its state and provides a getter method to retrieve the text content.
  3. The TextEditorCaretaker class manages the list of TextEditorMemento objects using a stack. It has methods to save the state (save()), restore the last saved state (undo()), and restore the next state (redo()).

Java Code

import java.util.Stack;

// Originator
class TextEditor {
    private String text;

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public TextEditorMemento save() {
        return new TextEditorMemento(text);
    }

    public void restore(TextEditorMemento memento) {
        text = memento.getText();
    }
}

// Memento
class TextEditorMemento {
    private String text;

    public TextEditorMemento(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

// Caretaker
class TextEditorCaretaker {
    private Stack<TextEditorMemento> mementoStack = new Stack<>();

    public void save(TextEditorMemento memento) {
        mementoStack.push(memento);
    }

    public TextEditorMemento undo() {
        if (!mementoStack.isEmpty()) {
            return mementoStack.pop();
        }
        return null;
    }
}

// Example usage
public class Main {
    public static void main(String[] args) {
        TextEditor textEditor = new TextEditor();
        TextEditorCaretaker caretaker = new TextEditorCaretaker();

        // Set initial text
        textEditor.setText("Hello, World!");

        // Save state
        caretaker.save(textEditor.save());

        // Modify text
        textEditor.setText("Updated text");

        // Print current text
        System.out.println("Current text: " + textEditor.getText());

        // Undo
        TextEditorMemento previousState = caretaker.undo();
        if (previousState != null) {
            textEditor.restore(previousState);
        }

        // Print text after undo
        System.out.println("Text after undo: " + textEditor.getText());
    }
}
Java

Benefits of Memento Design Pattern

The Memento design pattern offers several benefits, including:

  1. Encapsulation: The pattern promotes encapsulation by encapsulating the internal state of an object within the Memento object. This ensures that the state cannot be accessed or modified directly by other objects.
  2. Undo/Redo functionality: The Memento pattern enables undo/redo operations by saving snapshots of an object’s state at different points in time. It allows for easy restoration of previous states.
  3. Simplified state management: The pattern simplifies state management by externalizing the state storage to Memento objects. This reduces the complexity of maintaining and restoring state within the Originator object.

Applications of Memento Design Pattern

The Memento design pattern has various real-world applications in software development. Some notable use cases of the Memento pattern include:

  1. Undo/Redo functionality: The Memento pattern is commonly used to implement undo/redo operations in applications such as text editors, graphics editors, or collaborative document editing tools. It allows users to revert to previous states or redo actions that were undone.
  2. Checkpoint and save systems: Games often utilize the Memento pattern to implement checkpoints and save systems. It enables players to save their progress and restore the game state from a specific point, allowing them to continue gameplay or retry from a saved state.
  3. Transaction management: In database systems, the Memento pattern can be employed to manage transactions. Before modifying database records, a snapshot (Memento) of the original state is created. If the transaction fails, the system can restore the database state using the Memento.
  4. Browser history: Web browsers use the Memento pattern to manage the back and forward navigation functionality. The current page state is captured in a Memento object, which allows users to go back and forth between previously visited pages.
  5. Configuration management: Applications with dynamic configurations may use the Memento pattern to manage different settings and configurations. It enables the application to save and restore specific configurations or switch between different configurations easily.
  6. Collaborative editing: Memento can be beneficial in collaborative editing tools, where multiple users work on the same document simultaneously. It allows individual users to save their changes as Memento objects and roll back to previous versions or synchronize changes with other users.

Pros & Cons of Memento Design Pattern

ProsCons
State encapsulation: The Memento pattern encapsulates the state of an object, allowing it to be stored and restored without exposing its implementation details. This promotes good encapsulation and information hiding.Increased memory usage: Storing and managing Memento objects can consume memory, especially if the objects are large or numerous. It’s important to consider the memory overhead when applying the Memento pattern.
Undo/Redo functionality: The pattern is particularly useful for implementing undo/redo functionality, allowing users to revert actions or redo them in a systematic manner. It enables a flexible and intuitive user experience.Performance impact: The process of saving and restoring states using the Memento pattern can introduce performance overhead, especially when dealing with complex object structures. Care should be taken to optimize the implementation if performance is a critical concern.
State management: The Memento pattern simplifies the management of object states. It provides a structured way to capture and store states, allowing for easy retrieval and restoration when needed.Limited access to state: The Memento pattern restricts direct access to the internal state of an object, as it is encapsulated within the Memento. This can limit the ability to perform certain operations that require direct state access.
Maintainability and extensibility: By separating the state storage and restoration logic from the object itself, the Memento pattern improves code maintainability. It allows for easy addition of new features without modifying the original object’s implementation.
Coupling between Originator and Memento: In some implementations, the Originator may need to expose its internal state or provide specific methods to the Memento, which can introduce a certain level of coupling between the two classes.

Relation With Other Patterns

  • Command Pattern: The Memento pattern can be combined with the Command pattern to implement undo/redo functionality. The Command pattern encapsulates operations as objects, while the Memento pattern captures and restores the state of objects affected by these commands.
  • Originator-Prototype relationship: The Originator in the Memento pattern is sometimes implemented using the Prototype pattern. The Originator can create a Memento by cloning itself using the Prototype pattern, capturing the current state of the object.
  • Mediator pattern: The Memento pattern can be used in conjunction with the Mediator pattern to manage object states in a collaborative environment. The Mediator handles communication between objects, while the Memento captures and restores their states as required.
  • Iterator pattern: The Memento pattern can work in conjunction with the Iterator pattern to save and restore the state of an iterator. The Memento captures the iterator’s current position, allowing it to be restored later to continue iteration from that point.

Read More: https://www.scaler.com/topics/memento-design-pattern/