Definition

Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.

A Text Editor application

There is a text editor application which enables to add and format text. All such applications support the ‘undo’ operation. The undo operation undos the last performed operation and restores the text editor to the previous state.

To achieve this, each change (such as adding or removing text, changing font/color etc.,) to the editor must be stored externally. To restore the editor state back to one of its previous state, the corresponding snapshot will be restored.

Let us say we have a class TextEditorData.

One way to do this is to make all the fields or properties of the TextEditorData class public. After performing each operation, we can copy the data of the TextEditorData class and make a snapshot. To restore, we can pick the field’s value from the snapshot and assign it back to the TextEditorData.

The above is bad for obvious reasons. It makes all the fields public and it exposes the internals.

Using the Memento Design Pattern

Using the Memento design pattern gives us a way to capture and store an object’s (text editor data here) internal state so that we can restore it later. It enables us to do this without compromising on the encapsulation.

In this, we create a snapshot of the editor data before applying any operation to the editor. This snapshot represents the state of the text editor at a given point in time. We store this snapshot in a stack. To undo an operation, we pop a snapshot entry from the stack and restore the editor to that point in time. In other words, we set the editor’s internal data values from the values of the snapshot.

Let us look at some terminology involved in the Memento Design Pattern.

Originator

The Originator is the object whose state we are looking to save and restore. In our example, it is the text editor’s data (the text, color, font-size etc). An Originator has two methods - one to create a snapshot out of it and one to restore itself from a given snapshot.

Memento

Memento is a value object that contains the snapshot of the state created from the Originator i.e., it represents the state of the text editor at a point in time.

Caretaker

The Caretaker object works with the Originator and the Memento. Before applying any change to the Originator (the Editor), it gets a snapshot of it (memento). The Caretaker is responsible for performing the undo operation. To undo, it gets the correct snapshot object (memento) and calls the Originator with it to restore it.

Sequence of operations

  1. The Caretaker asks for a memento from the Originator before performing an operation.
  2. The Originator creates and returns a memento. This object contains the state of the Originator.
  3. When performing the undo operation, the Caretaker gives the memento to the Originator.
  4. The Originator changes its internal fields’ values from the memento.

Memento-Design-Pattern-Sequence

Implementation of the Memento Design Pattern

Let us look at implementing a simple Text Editor. The TextEditorData has just the text and the Color of the text. It is the Originator. Hence, it has methods to create a memento and restore from a memento.

public class EditorData {
    private String text;
    private Color color;

    public EditorData() {

    }
    public EditorData(String text, Color color) {
        this.text = text;
        this.color = color;
    }

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

    public void setColor(Color color) {
        this.color = color;
    }

    public Snapshot createSnapshot() {
        return new Snapshot(text, color);
    }

    public void restoreFromSnapshot(Snapshot snapshot) {
        this.text = snapshot.text;
        this.color = snapshot.color;
    }

    //for debugging
    public void print() {
        System.out.println("Text: " + text);
        System.out.println("Color:" + color);
    }

    public static class Snapshot {
        private String text;
        private Color color;

        public Snapshot(String text, Color color) {
            this.text = text;
            this.color = color;
        }
    }
}
public enum Color {
    BLACK,
    BLUE,
    RED
}

The createSnapshot method creates and returns an object of type Snapshot. The Snapshot is a public static nested class of the EditorData. It has the same fields/properties of the EditorData class. When creating a snapshot, EditorData passes the values for text and color to the Snapshot object.

The restoreFromSnapshot takes a Snapshot object and changes the current EditorData’s text and color values from the snapshot’s.

The print method is added to print the current state of the EditorData.

Now, let us look at the Caretaker object.

public class Editor {
    private EditorData editorData;
    private Stack<EditorData.Snapshot> snapshots;

    public Editor(EditorData editorData) {
        this.editorData = editorData;
        this.snapshots = new Stack<>();
    }

    public void changeText(String newText) {
        //Create and store current state
        snapshots.push(editorData.createSnapshot());

        editorData.setText(newText);
    }

    public void changeColor(Color newColor) {
        //Create and store current state
        snapshots.push(editorData.createSnapshot());

        editorData.setColor(newColor);
    }

    public void undo() {
        if (snapshots.isEmpty()) {
            System.out.println("Already back to original state");
        }
        editorData.restoreFromSnapshot(snapshots.pop());
    }

    //for debugging
    public void printEditorContent() {
       editorData.print();
    }
}

The Editor class is the Caretaker. Before changing the text or the color, it asks the EditorData a memento (snapshot) and pushes it into a stack. To undo, it gets the top memento (snapshot) from the stack that contains the previous editor’s state and calls restoreFromSnapshot passing the snapshot.

Let us look at this in action

public class Main {
    public static void main(String[] args) {
        EditorData editorData = new EditorData("You are learning Memento pattern", Color.BLACK);
        Editor editor = new Editor(editorData);

        //1.Change text
        editor.changeText("You are learning Memento pattern on Java Developer Central");

        //2.Change color
        editor.changeColor(Color.BLUE);

        //3.Change color again
        editor.changeColor(Color.RED);

        editor.printEditorContent();

        editor.undo();
        editor.printEditorContent();

        editor.undo();
        editor.printEditorContent();

        editor.undo();
        editor.printEditorContent();
    }
}

Running the above prints,

Text: You are learning Memento pattern on Java Developer Central
Color:RED
Text: You are learning Memento pattern on Java Developer Central
Color:BLUE
Text: You are learning Memento pattern on Java Developer Central
Color:BLACK
Text: You are learning Memento pattern
Color:BLACK

First, we create the Editor with default text in black. Next, we change the text following which we change the color twice (to Blue and then Red).

We start off by printing the current state of the editor. Next when we undo and print the editor contents, we can see that the color of the text is restored back to Blue. A further undo puts the color to Black. A final undo restores the editor’s text to the version when we created the EditorData object.

Structure

MementoClassDiagram

Participants

Memento (EditorData.Snapshot/EditorMemento)

  • Stores the internal state of the Originator object.
  • Encapsulates the state and does not allow any object other than the Originator to read the data.

Originator(EditorData)

  • Creates a memento object containing the internal state of it (Originator).
  • Restores its state from a memento.

Caretaker(Editor)

  • Keeps track of snapshots (mementos) to restore.
  • Has limited interaction with the memento.

Alternate implementation

In the implementation of the Memento Design Pattern, the memento is modelled as a static nested class. It is an immutable class. The Caretaker object cannot mutate the Memento values.

There is one more possible implementation that provides a stricter encapsulation.

In this, we create an interface for the Memento and Originator. The Memento has a restore method and the Originator has a method to create a memento.

public interface Memento {
    void restore();
}
public interface Originator {
    Memento createSnapshot();
}

The EditorData is the Originator, and it creates a Memento object. But, the difference here is that the Memento is a separate public class. The EditorData passes itself as an argument in addition to the state it passes.

When restoring from a memento, the Memento is responsible for restoring the state of the Originator it has. The Memento calls the setter methods on the Originator reference it has to restore it.

public class EditorData implements Originator {
    private String text;
    private Color color;

    public EditorData() {

    }

    public EditorData(String text, Color color) {
        this.text = text;
        this.color = color;
    }

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

    public void setColor(Color color) {
        this.color = color;
    }

    public Memento createSnapshot() {
        return new EditorMemento(this, text, color);
    }

    //for debugging
    public void print() {
        System.out.println("Text: " + text);
        System.out.println("Color:" + color);
    }
}
public class EditorMemento implements Memento {
    private final EditorData editorData;
    private final String text;
    private final Color color;

    public EditorMemento(EditorData editorData, String text, Color color) {
        this.editorData = editorData;
        this.text = text;
        this.color = color;
    }
    @Override
    public void restore() {
        editorData.setText(text);
        editorData.setColor(color);
    }
}
public class Editor {
    private EditorData editorData;
    private Stack<Memento> snapshots;

    public Editor(EditorData editorData) {
        this.editorData = editorData;
        this.snapshots = new Stack<>();
    }

    public void changeText(String newText) {
        //Create and store current state
        snapshots.push(editorData.createSnapshot());

        editorData.setText(newText);
    }

    public void changeColor(Color newColor) {
        //Create and store current state
        snapshots.push(editorData.createSnapshot());

        editorData.setColor(newColor);
    }

    public void undo() {
        if (snapshots.isEmpty()) {
            System.out.println("Already back to original state");
        }
        snapshots.pop().restore();
    }

    //for debugging
    public void printEditorContent() {
        editorData.print();
    }
}

With this, the Caretaker only interacts with the Memento interface which is cleaner. Also, note that the Caretaker (Editor) now just calls the restore method on the memento rather than passing it to the Originator.

public class Main {
    public static void main(String[] args) {
        EditorData editorData = new EditorData("You are learning Memento pattern", Color.BLACK);
        Editor editor = new Editor(editorData);

        //1.Change text
        editor.changeText("You are learning Memento pattern on Java Developer Central");

        //2.Change color
        editor.changeColor(Color.BLUE);

        //3.Change color again
        editor.changeColor(Color.RED);

        editor.printEditorContent();

        editor.undo();
        editor.printEditorContent();

        editor.undo();
        editor.printEditorContent();

        editor.undo();
        editor.printEditorContent();

    }
}

Running the above code produces the same output as seen earlier.

Conclusion

In this post, we looked at the Memento Design Pattern. We learnt that it is used when we have to save the state of an object and restore it later without affecting the encapsulation in any way. We looked at an example of the application of the Memento in a text editor application that has the text and the color of the text as state. And We looked at two possible implementations of the pattern.

Check out the other design pattern posts on Java Developer Central.

References