Implementing Undo-Redo Functionality with Memento Design Pattern

Snippet of programming code in IDE
Published on

Introduction

In software development, it is often necessary to implement undo-redo functionality, allowing users to revert back and forth between different states of an application. One way to achieve this is by using the Memento design pattern. The Memento pattern is a behavioral design pattern that enables us to save and restore the state of an object without violating encapsulation.

In this article, we will explore how to implement undo-redo functionality using the Memento design pattern in Java. We will discuss the structure and purpose of the Memento pattern, and then provide a step-by-step guide on how to implement it.

The Memento Design Pattern

The Memento design pattern is based on three main components:

  1. Originator: This is the object whose state we want to save and restore. It has a method to create a Memento object representing its current state, as well as a method to restore its state from a Memento object.

  2. Memento: This is an object that stores the state of the Originator. It provides methods for getting and setting the state.

  3. Caretaker: This is the object that holds and manages the Memento objects. It is responsible for storing and retrieving Memento objects, and it provides methods for undoing and redoing operations.

Steps to Implement Undo-Redo Functionality

Now let's walk through the steps to implement undo-redo functionality using the Memento design pattern.

Step 1: Create the Originator class

First, we need to create the Originator class, which is the object we want to save and restore its state. This class should have a method to create a Memento object representing its current state, as well as a method to restore its state from a Memento object.

Here's an example implementation of the Originator class:

public class TextEditor {
    private String text;

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

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

    public String getText() {
        return text;
    }

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

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

In this example, we have a TextEditor class that represents the object whose state we want to save and restore. It has a setText method to set the text, a getText method to get the text, a createMemento method to create a Memento object, and a restoreMemento method to restore the state from a Memento object.

Step 2: Create the Memento class

Next, we need to create the Memento class, which stores the state of the Originator. This class should have methods for getting and setting the state.

Here's an example implementation of the Memento class:

public class TextEditorMemento {
    private final String text;

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

    public String getText() {
        return text;
    }
}

In this example, we have a TextEditorMemento class that represents the Memento object. It has a getText method to get the text stored in the Memento.

Step 3: Create the Caretaker class

Next, we need to create the Caretaker class, which holds and manages the Memento objects. This class is responsible for storing and retrieving Memento objects, and it provides methods for undoing and redoing operations.

Here's an example implementation of the Caretaker class:

import java.util.Stack;

public class Caretaker {
    private final Stack<TextEditorMemento> mementos;
    private int currentIndex;

    public Caretaker() {
        this.mementos = new Stack<>();
        this.currentIndex = -1;
    }

    public void addMemento(TextEditorMemento memento) {
        while (mementos.size() > currentIndex + 1) {
            mementos.pop();
        }
        mementos.push(memento);
        currentIndex++;
    }

    public TextEditorMemento undo() {
        if (currentIndex > 0) {
            currentIndex--;
        }
        return mementos.get(currentIndex);
    }

    public TextEditorMemento redo() {
        if (currentIndex < mementos.size() - 1) {
            currentIndex++;
        }
        return mementos.get(currentIndex);
    }
}

In this example, we have a Caretaker class that holds and manages the Memento objects. It has a Stack to store the Memento objects, an addMemento method to add a Memento object to the stack, an undo method to undo the last operation, and a redo method to redo the last operation.

Step 4: Testing the implementation

Finally, we can test our implementation by creating an instance of the TextEditor class, as well as an instance of the Caretaker class. We can then perform some operations on the TextEditor, and use the Caretaker to store and retrieve Memento objects to implement undo-redo functionality.

Here's an example test code:

public class Main {
    public static void main(String[] args) {
        TextEditor textEditor = new TextEditor();
        Caretaker caretaker = new Caretaker();

        // Perform some operations
        textEditor.setText("Hello");
        caretaker.addMemento(textEditor.createMemento());

        textEditor.setText("Hello World");
        caretaker.addMemento(textEditor.createMemento());

        // Undo the last operation
        textEditor.restoreMemento(caretaker.undo());
        System.out.println(textEditor.getText()); // Output: Hello

        // Redo the last operation
        textEditor.restoreMemento(caretaker.redo());
        System.out.println(textEditor.getText()); // Output: Hello World
    }
}

In this example, we create a TextEditor object and a Caretaker object. We then set the text in the TextEditor to "Hello" and store the Memento using the Caretaker. We perform another operation by setting the text to "Hello World" and store another Memento. Finally, we undo the last operation and redo it to demonstrate the undo-redo functionality.

Conclusion

Implementing undo-redo functionality is a common requirement in many applications. By using the Memento design pattern, we can easily save and restore the state of an object without violating encapsulation. In this article, we discussed the structure and purpose of the Memento pattern, and provided a step-by-step guide on how to implement undo-redo functionality using the Memento pattern in Java.