Link Search Menu Expand Document

The Memento Design Pattern

The Memento Design Pattern is designed to externalize the internal state of the object in order to be stored or to restore an object to some previous version of its state.


The Memento Pattern is also known as The Token Design Pattern.

What problems does it solve? Why to use it?

Allows you to store history or restore object to it’s previous state by externalizing its internal state.

Glossary:

  • Memento - the snapshot/ the state of the object (originator), should be immutable, so that once created, could not be changed
  • Originator - the objects, whose states we are going to keep
  • Caretaker - responsible for storing the states of the originator and restoring them thanks to the memento object

Pros:

  • to keep track of the versions
  • to restore to a previous state

Cons:

  • additional operations to create/ keep memento-s (snapshots)
  • increases the memory usage (because we should keep more states of the object)
  • more code to maintain

How to recognize it?

When you call a behavioral method, which internally changes the state of an instance.

TextFile file1 = new TextFile("Draft1.txt");
file1.writeContent("This is my initial content");
TextFileVersionManager.commit(file1, "My initial version");

file1.writeContent("Content Modified 2");
TextFileVersionManager.commit(file1, "Version 2");

TextFileVersionManager.restore(file1, 0);

Examples from Java API

java.util.Date (the setter methods do that, Date is internally represented by a long value)
All implementations of java.io.Serializable
All implementations of javax.faces.component.StateHolder

Scenarios

  • When you need to keep track of the version (history)
  • When you need to restore a previous state of an object (to rollback/ to revert/ to undo changes)

Example 1

We can create a simple Version Control System (liteCVS). We will commit files and keep track of the changes with each commit. Then we can list all commits and revert a state from specific commit.

Source Code

1). Create the Originator

/**
 * The Originator
 */
@ToString
public class TextFile {

    @Getter
    private String id;

    @Getter
    private String name;

    @Getter
    private String content;

    public TextFile(String name) {
        this.id = UUID.randomUUID().toString();
        this.name = name;
    }

    public TextFileMemento createSnapShot(int serialNo, String commitMessage){
        return new TextFileMemento(serialNo, this.id, this.name, this.content, commitMessage);
    }

    public void writeContent(String updatedContent) {
        this.content = updatedContent;
    }
}

2). Create the immutable memento object

/**
 * The Memento Object - should be immutable
 */
@ToString
public class TextFileMemento {

    private int serialNo;

    @Getter
    private String id;

    @Getter
    private String name;

    @Getter
    private String content;

    private String commitMessage;
    private Date commitTime;

    public TextFileMemento(int serialNo, String id, String name, String content, String commitMessage) {
        this.serialNo = serialNo;

        this.id = id;
        this.name = name;
        this.content = content;

        this.commitMessage = commitMessage;
        this.commitTime = new Date();
    }
}

3). Create the Caretaker

/**
 * The Caretaker
 */
public final class TextFileVersionManager {

    private static Map<String, List<TextFileMemento>> states = new HashMap<>();

    private TextFileVersionManager() {
    }

    public static void commit(TextFile file, String commitMessage) {
        List<TextFileMemento> snapshots = states.get(file.getId());
        if (snapshots == null) {
            snapshots = new ArrayList<>();
            states.put(file.getId(), snapshots);
        }

        states.get(file.getId()).add(file.createSnapShot(snapshots.size(), commitMessage));

        System.out.println("Commit created: " + file.getName());
    }

    public static void listCommits(TextFile file) {
        List<TextFileMemento> commits = states.get(file.getId());
        if (commits == null || commits.isEmpty()) {
            System.out.println("No commits found: " + file.getName());
        } else {

            System.out.println("Commits found: " + file.getName());
            for (TextFileMemento commit : commits) {
                System.out.println(commit);
            }
        }
    }

    public static void restore(TextFile file, int commitSerialNo) {
        List<TextFileMemento> commits = states.get(file.getId());
        if (commits != null && commitSerialNo > -1) {
            TextFileMemento versionToRestore = commits.get(commitSerialNo);
            file.writeContent(versionToRestore.getContent());

            states.put(file.getId(), commits.subList(0, commitSerialNo));

            System.out.println("Rollback to: " + commitSerialNo);
        }
    }
}

4). The Demo

public class _Main {

    public static void main(String[] args) {
        TextFile file1 = new TextFile("Draft1.txt");
        file1.writeContent("This is my initial content");

        TextFileVersionManager.commit(file1, "My initial version");
        TextFileVersionManager.listCommits(file1);

        file1.writeContent("Content Modified 2");
        TextFileVersionManager.commit(file1, "Version 2");
        TextFileVersionManager.listCommits(file1);

        TextFileVersionManager.restore(file1, 0);
        TextFileVersionManager.listCommits(file1);

        System.out.println("Current state of the file: " + file1);
    }
}

Output:

Commit created: Draft1.txt
Commits found: Draft1.txt
TextFileMemento(serialNo=0, id=56eeab5a-6d59-435b-8fbd-0d7f3b137ff4, name=Draft1.txt, content=This is my initial content, commitMessage=My initial version, commitTime=Mon Jul 08 18:48:17 EEST 2019)
Commit created: Draft1.txt
Commits found: Draft1.txt
TextFileMemento(serialNo=0, id=56eeab5a-6d59-435b-8fbd-0d7f3b137ff4, name=Draft1.txt, content=This is my initial content, commitMessage=My initial version, commitTime=Mon Jul 08 18:48:17 EEST 2019)
TextFileMemento(serialNo=1, id=56eeab5a-6d59-435b-8fbd-0d7f3b137ff4, name=Draft1.txt, content=Content Modified 2, commitMessage=Version 2, commitTime=Mon Jul 08 18:48:17 EEST 2019)
Rollback to: 0
No commits found: Draft1.txt
Current state of the file: TextFile(id=56eeab5a-6d59-435b-8fbd-0d7f3b137ff4, name=Draft1.txt, content=This is my initial content)