Menu Close

Object Oriented Design Pattern: Memento

memento design pattern

Introduction

The Memento pattern is a behavioral software design pattern. It is used to record, or memorize, the internal state of an object. The pattern makes an object store the internal state of another object. One of the most clear examples of this, is undo and redo in about any application, such as about any office application. The storage of the state can be achieved without violating encapsulation. The so called Memento object stores the internal state of the Originator object.

  • Memento: A snapshot of the originator.
  • Originator: The object that needs saved states.
  • Caretaker: Manages memento objects.

Use Cases

  • You want to implement Undo and Redo.
  • You want to implement transactional rollbacks.

Pitfalls

  • Potential high memory usage.

Java Example

To demonstrate the structure and workings of the memento design patter I have created a JTextArea that auto saves the state of the text area every so many seconds. For this reason, you can use the control + z key combination to undo and the control + r key combination to redo.

Main Class

The Main class does the following:

  • Listen to key input.
  • Instantiate the Originator which holds the TextArea and states of the TextArea.
  • Instantiate the Caretaker which keeps track of the snapshots (Memento objects) which are states of the Originator.
  • Instantiate the AutoSaveManager which automatically creates snapshots.
package com.chosengambit.designpatterns;

import com.chosengambit.memento.AutoSaveManager;
import com.chosengambit.memento.Caretaker;
import com.chosengambit.memento.Originator;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JTextArea;

public class Main implements KeyListener {
    
    private static Originator originator;
    private static Caretaker caretaker;    
    private static AutoSaveManager autoSaveManager;

    public static void main(String[] args) {
        
        try {
            
            // UI components
            JFrame frame = new JFrame("Text Editor");
            JTextArea textArea = new JTextArea();
            
            // Setup key listener
            Main listener = new Main();
            setupKeyListener(textArea, listener);
            
            // Originator and Caretaker objects from Memento design pattern
            originator = new Originator(frame, textArea);
            caretaker = new Caretaker();        
            
            // Setup auto-save manager
            autoSaveManager = new AutoSaveManager(originator, caretaker);
        }
        catch (Exception e) {
            System.out.println(e.toString());
        }       
    }
    
    @Override
    public void keyPressed(KeyEvent e) {
        
        if (e.isControlDown()) {
            if (e.getKeyCode() == KeyEvent.VK_R) {
                originator.setState(caretaker.redo());
                autoSaveManager.restartTimer();
            }
            else if (e.getKeyCode() == KeyEvent.VK_Z) {
                originator.setState(caretaker.undo());
                autoSaveManager.restartTimer();
            }            
        }
    }
            
    private static void setupKeyListener(Component component, KeyListener listener) {
        component.addKeyListener(listener);
    }

    @Override
    public void keyReleased(KeyEvent e) {}
    
    @Override
    public void keyTyped(KeyEvent e) {}

}
Java

Memento Class

Very straightforward, our Memento class does save on thing of the state of the originator, which is the text inside the TextArea. This could be extended.

package com.chosengambit.memento;

public class Memento {
    
    private final String state;
    
    public Memento(String state) {
        this.state = state;
    }
    
    public String getState() {
        return this.state;
    }   
}
Java

Originator Class

  • Holds the TextArea and a state of the TextArea.
  • Can return the state as a Memento object.
  • Can load set the state which is a Memento object.
package com.chosengambit.memento;

import java.awt.Container;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JTextArea;

public class Originator {
    
    private final JFrame frame;
    private final JTextArea textArea;
        
    public Originator(JFrame frame, JTextArea textArea) {
        this.frame = frame;
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);
        this.textArea = textArea;
        setupTextArea();
    }
    
    public void setState(Memento memento) {
        if (memento != null)
            this.textArea.setText(memento.getState());
    }
    
    public Memento getState() {
        return new Memento(this.textArea.getText());
    }

    private void setupTextArea() {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(640,480));        
        Container contentPane = frame.getContentPane();
        contentPane.add(textArea);        
        frame.pack();
        frame.setVisible(true);
    }
}
Java

Caretaker Class

  • Holds a List of Memento objects, which are saved states for the Originator.
  • Implements undo and redo, which both return a saved state (Memento object).
  • Can save a new state.
package com.chosengambit.memento;

import java.util.ArrayList;
import java.util.List;

public class Caretaker {
    
    private int historyIndex = 0;
    private final List<Memento> history = new ArrayList<>();   
    
    public Memento undo() {
        historyIndex = --historyIndex < 0 ? 0 : historyIndex;
        return loadState(historyIndex);
    }
    
    public Memento redo() {
        historyIndex = ++historyIndex >= history.size() ? history.size()-1 : historyIndex;
        return loadState(historyIndex);        
    }
    
    private Memento loadState(int index) {
        if (index >= 0 && index < history.size())
            return history.get(index);
        return null;
    }    
    
    public void saveState(Memento snapshot) {        
            
        String current = snapshot.getState();
        String last = !history.isEmpty() ? history.getLast().getState() : null;

        if (last == null || !current.equals(last)) {

            // trim future states
            while (history.size() > historyIndex + 1) {
                history.remove(history.size() - 1);
            }

            history.add(snapshot);
            historyIndex = history.size()-1;
            System.out.println("Perform auto-save ");                   
        }        
    }    
}
Java

AutoSaveManager Class

This class is not part of the pattern, but I made it to have a mechanism that creates new save states.

  • Every 5 seconds a new save state is made.
  • You can restart the auto save timer.
package com.chosengambit.memento;

import javax.swing.Timer;

public class AutoSaveManager {
    
    private Timer saveTimer;
    
    public AutoSaveManager(Originator originator, Caretaker caretaker) {
        
        caretaker.saveState(new Memento(""));
        
        this.saveTimer = new Timer(5 * 1000, (e) -> {            
            Memento snapshot = originator.getState();
            caretaker.saveState(snapshot);
        });
        saveTimer.setRepeats(true);
        saveTimer.start();            
    }
    
    public void restartTimer() {
        this.saveTimer.restart();
    }    
}
Java

Class Diagram

Class diagram: Memento design pattern

Conclusion

I tried to create a simple demonstration of the Memento design pattern, with an (oversimplified) auto-save mechanism. The Originator is the object that you want to keep track of states for. The Caretaker takes care of these states. You need an overarching/umbrella class to direct these objects. In our case, we had a AutoSaveManager automatically make snapshots at an interval.

References

Freeman, E., Bates, B., & Sierra, K. (2004). Head first design patterns.

Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software.

Wikipedia contributors. (2024). Software design pattern. Wikipedia. https://en.wikipedia.org/wiki/Software_design_pattern

Related Posts