Memento Design Pattern

The Memento Design Scheme is employed to safeguard the internal condition of an entity for subsequent restoration to historical states when necessary. It relies on a Memento entity to house its condition. The details of the state contained within the memento entity remain inaccessible from external entities, thereby upholding encapsulation. This safeguards the integrity of the archived state information.

This scheme proves beneficial when incorporating features like undo capabilities, preserving a record of alterations, or providing a mechanism for object snapshots. In this guide, we will delve into the Memento Design Scheme using Java. We'll delve into its structure, execution, and applications. Furthermore, we'll touch on recommended practices for executing the scheme and underscore its benefits.

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.


Benefits of Memento Design Pattern Approach

The Memento Design Pattern approach brings forth numerous advantages that contribute to the development of adaptable and sustainable software systems:
  • Revocable/Repeatable Capability : A key application of the Memento Design Approach is the realization of capabilities such as revocation and repetition. This allows users to return an object's status to a prior point or progress to a more recent state.

  • Insulation of Status : The Memento Design Approach segregates an object's internal state from the wider system. This encapsulation guarantees that the state remains inaccessible and unalterable by external classes.

  • Snapshot Mechanism : The approach introduces a snapshot mechanism, enabling the originator to capture its internal state at a specific moment. This proves useful for establishing checkpoints or implementing versioning systems.

  • Ease of Maintenance : By encapsulating the state within mementos, the internal workings of the originator stay concealed. This encapsulation aids in maintaining the code, as alterations to the originator's implementation don't impact the caretaker.

  • Support for Transactional Behavior : Mementos can facilitate the implementation of transactional behavior, where a sequence of operations can be grouped, and the system can be reverted to a previous state if any operation encounters an issue.

  • Flexibility in State Restoration : The approach allows flexibility in restoring an object's state. Various mementos can be employed to achieve specific states, providing detailed control over the restoration process.

  • Separation of Responsibilities : The Memento Design Approach advocates for the separation of responsibilities by segregating state-related functionalities. This division simplifies the design, enabling each component to concentrate on its designated responsibilities.

  • History Tracing : The caretaker can maintain a history of an object's state alterations by keeping a collection of mementos. This history tracking proves valuable in scenarios where a comprehensive record of changes is necessary.
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

For a sturdy and sustainable implementation of the Memento Design Practice, adhere to these recommended approaches:
  • Construct the memento class with immutability in mind. This guarantees that the state within a memento remains unalterable once created. Immutability assures that the memento captures a consistent snapshot of the originator's state.

  • Thoughtfully select the state to include in the memento. Only incorporate the essential state required for reinstating the originator to a coherent and meaningful condition.

  • Restrict the visibility of the memento class. Preferably, assign package-private or private access to the memento class, with access to its internals regulated through well-defined methods.

  • If your application involves versioning or rollback mechanisms, contemplate integrating version information into your mementos. This data proves helpful in managing diverse versions of an object's state.

  • Entrust the originator with the responsibility of overseeing its state and crafting mementos. Ensure the originator delivers a lucid and consistent API for safeguarding and reinstating its state.

  • Confine access to mementos within the originator and caretaker. External entities should not directly access mementos to avert inadvertent alterations.

  • The caretaker should not depend on the internal structure of the memento. Interaction with the memento should exclusively occur through well-defined methods supplied by the originator.

  • If your application involves persisting or transmitting mementos, contemplate endowing the memento class with serializability. Serialization facilitates the storage or transmission of mementos.

  • Select explicit and meaningful names for methods in the memento class. Employ naming conventions like getState and setState to provide clarity on the purpose of these methods.
Related Topics
Bridge Design Pattern
Facade Design Pattern
Interpreter Design Pattern
Factory Design Pattern
Abstract Factory Design Pattern
List of Design Patterns