Memento Design Pattern

The Memento Design Pattern is used to store the internal state of an object so that it can be used later to restore the object back to historical state when needed. It uses a Memento object to store it's state. The state information in the memento object is not accessible form outside of the object and thus honors encapsulation. This protects the integrity of the saved state data.

This pattern is useful when you need to implement undo functionality, maintain a history of changes, or provide a snapshot mechanism for an object. In this tutorial, we will explore the Memento Design Pattern in Java. We'll cover its structure, implementation, and use cases. Additionally, we'll discuss best practices for implementing the pattern and highlight its advantages.

Structure of the Memento Design Pattern

The Memento Design Pattern involves the following key components:
  • Originator : The object whose state we want to save for future use. It creates memento object capturing its internal state. It also uses previously saved memento to restore to its previous state.
  • CareTaker : The object which maintains the history of the states of Originator. It provides the data store for saving and restoring internal states. It cannot read or change the data of a memento object.
  • Memento : It is used to store the internal states of Originator at any moment of time. It is created and retrieved by the Originator and stored and maintained by Caretaker.
The Originator will store the state information in the Memento object and pass it to a CareTaker for storing it and retrieve old state information when it needs to rollback.


Advantages of Memento Design Pattern

The Memento Design Pattern offers several advantages that contribute to the development of flexible and maintainable software systems:
  • Undo/Redo Functionality : One of the primary use cases of the Memento Design Pattern is the implementation of undo and redo functionality. It allows users to revert an object's state to a previous state or move forward to a more recent state.

  • Isolation of State : The Memento Design Pattern isolates the internal state of an object from the rest of the system. This encapsulation ensures that the state is not directly accessible or modifiable by external classes.

  • Snapshot Mechanism : The pattern provides a snapshot mechanism, allowing the originator to capture its internal state at a specific point in time. This can be useful for creating checkpoints or implementing versioning systems.

  • Maintainability : By encapsulating the state in mementos, the originator's internal implementation details remain hidden. This encapsulation contributes to the maintainability of the code, as changes to the originator's implementation do not affect the caretaker.

  • Supports Transactional Behavior : Mementos can be used to implement transactional behavior, where a series of operations can be grouped, and the system can be rolled back to a previous state if any operation fails.

  • Flexibility in Restoring State : The pattern provides flexibility in restoring an object's state. Different mementos can be applied to achieve specific states, allowing for granular control over the restoration process.

  • Separation of Concerns : The Memento Design Pattern promotes the separation of concerns by isolating state-related functionality. This separation simplifies the design and allows each component to focus on its specific responsibilities.

  • History Tracking : The caretaker can keep a history of an object's state changes by maintaining a collection of mementos. This history tracking is valuable for scenarios where a complete record of changes is needed.
By leveraging these advantages, the Memento Design Pattern becomes a valuable tool for managing the state of objects in a way that supports undo functionality, history tracking, and other scenarios where the ability to capture and restore state is crucial. Its flexibility and encapsulation of state-related concerns contribute to the overall robustness and maintainability of software systems.

When we should use Memento Pattern

  • When we want to restore back an abject to its previous state. It is used heavily in GUI applications for doing undo and rollback operations.
  • To maintain the atomicity of a database transaction. If a transaction failed in intermediate steps then we have to rollback all the operations performed by the transaction handler till now.
  • When we want to maintain a history of states of an object.
  • When we don’t want to expose the internal state of an object.

Implementation of Memento Design Pattern

Memento Design Pattern UML Diagram Memento.java
public class Memento {
    private double temperature;
    private double pressure;
    private double volume;

    public Memento(double temp, double pressure, double volume){
        this.temperature = temp;
        this.pressure = pressure;
        this.volume = volume;
    }

    public double getTemperature(){
        return temperature;
    }
 
    public void printMemento(){
        System.out.println("State : [ Temperature = " + temperature
            + ", Pressure = " + pressure + ", Volume = " + volume + "]"); 
    }
 
    public double getPressure(){
       return pressure;
    }
 
    public double getVolume() {
       return volume;
    }
}

Originator.java
public class Originator {
    /*
     * Temperature, Pressure and Volume defines 
     * the state of a system under observation 
     */
    private double temperature;
    private double pressure;
    private double volume;

    public void setState(double temp, double pressure, double volume){
        this.temperature = temp;
        this.pressure = pressure;
        this.volume = volume;
    }
    
    public void printState(){
        System.out.println("State : [ Temperature = " + temperature
            + ", Pressure = " + pressure + ", Volume = " + volume + "]"); 
    }
 
    public Memento saveToMemento(){
        return new Memento(temperature, pressure, volume);
    }

    public void restoreStateFromMemento(Memento m){
        this.temperature = m.getTemperature();
        this.pressure = m.getPressure();
        this.volume = m.getVolume();
    }
}

CareTaker.java
import java.util.Map;
import java.util.Map.Entry;
import java.util.HashMap;

public class CareTaker {
    private int counter;
    private Map<Integer, Memento> mementoMap 
        = new HashMap<Integer, Memento>();

    public CareTaker() {
        counter = 1;      
    }
   
    public void addState(Memento m){
        mementoList.put(counter, m);
        counter++;
    }
    
    public void removeState(int i) {
     mementoMap.remove(i);
    }
    
    public void printAllSavedState() {
     System.out.println("------ Saved States------");
     for(Entry<Integer, Memento> entry : mementoMap.entrySet()){
         System.out.println("State " + entry.getKey()); 
         entry.getValue().printMemento();      
     }
     System.out.println("-------------------------");
    }

    public Memento getState(int i){
        return mementoMap.get(i);
    }
}

MementoPatternExample.java
public class MementoPatternExample {
    public static void main(String args[]) {
        Originator originator = new Originator();
        CareTaker careTaker = new CareTaker();
        // Set initial state of system
        originator.setState(10.5, 5.4, 100.3);
        // Save initial state of system
        careTaker.addState(originator.saveToMemento());

        // Change state of system 
        originator.setState(15.5, 3.1, 105.1);
        originator.setState(6.2, 8.3, 99.9);
        originator.setState(8.4, 7.2, 111.0);

        // Second Check point, Save state of system again 
        careTaker.addState(originator.saveToMemento());

        // Change state of system 
        originator.setState(12.5, 2.2, 123.4);
        // Print all saved states of syystem
        careTaker.printAllSavedState();

        // Printing current state of system 
        System.out.println("------ Current State------");
        originator.printState();
        // Restore state of system to initial state
        originator.restoreStateFromMemento(careTaker.getState(1));
        // Printing current state of system after restoring 
        System.out.println("------ State after Restoration------");
        originator.printState();
    }
}

Output

 ------ Saved States------
State 1
State : [ Temperature = 10.5, Pressure = 5.4, Volume = 100.3]
State 2
State : [ Temperature = 8.4, Pressure = 7.2, Volume = 111.0]
-------------------------
------ Current State------
State : [ Temperature = 12.5, Pressure = 2.2, Volume = 123.4]
------ State after Restoration------
State : [ Temperature = 10.5, Pressure = 5.4, Volume = 100.3]

Best Practices of Memento Design Pattern

To ensure a robust and maintainable implementation of the Memento Design Pattern, follow these best practices:
  • Design the memento class to be immutable. This ensures that once a memento is created, its state cannot be altered. Immutability guarantees that the memento captures a consistent snapshot of the originator's state.

  • Carefully choose what state to capture in the memento. Include only the state that is necessary for restoring the originator to a consistent and meaningful state.

  • Limit the visibility of the memento class. Ideally, the memento class should have package-private or private access, and access to its internals should be controlled through well-defined methods

  • If your application involves versioning or rollback mechanisms, consider adding version information to your mementos. This information can help in managing different versions of an object's state.

  • The originator is responsible for managing its state and creating mementos. Ensure that the originator provides a clear and consistent API for saving and restoring its state.

  • Limit the access to mementos within the originator and caretaker. External classes should not have direct access to mementos to prevent unintended modifications.

  • The caretaker should not rely on the internal structure of the memento. It should interact with the memento solely through well-defined methods provided by the originator.

  • If your application involves persisting or transmitting mementos, consider making the memento class serializable. Serialization allows mementos to be stored or sent across a network.

  • Choose clear and meaningful names for methods in the memento class. Naming conventions such as getState and setState provide clarity regarding the purpose of the methods.
Related Topics
Bridge Design Pattern
Facade Design Pattern
Interpreter Design Pattern
Factory Design Pattern
Abstract Factory Design Pattern
List of Design Patterns