Memento Design Pattern
Overview
The memo design pattern is a behavioural design pattern and is also known as Memento design pattern. The memo design pattern is a software design pattern that provides flexibility to restore an object to its previous state (undo) or store the object's history to understand how it mutated over several operations and restore to a specific state when required. The internal states (data members) of the object (necessary to restore the object) are stored by another object and is used to restore the original object to a previous checkpoint. The memento design pattern is implemented with the help of three objects, i.e., Originator, Caretaker, and a memento. We will understand each of these objects later in the article.
When Will We Need the Memo Design Pattern?
Memo design pattern helps store the internal state of an object outside the object itself without violating the object's encapsulation. This makes it useful in applications where we might need to restore the state of an object or access it without changing the current state of the object. Let us first try to understand some problems that can be solved using this design pattern.
Problems Memo Design Pattern Can Solve Compared to Other Design Patterns
- The object's internal state should be saved externally, which will allow restoring the object to this state later.
- This must be done without violating the object's encapsulation because an encapsulated object hides its representation (data members) inside the object and can't be accessed from outside the object.
Memento design pattern solves this problem by creating an object (Originator) responsible for
- saving the internal states of an object.
- restoring to a previous state from an object (memento).
Some of the everyday use cases of memo design patterns are
- Archiving when playing a game.
- Undo (ctrl + Z) in windows and text editors.
- Transaction management of databases to roll back a transaction and go to the previous state if it fails or is aborted.
- Pseudo-random number generator (PRNG) in which each consumer of the PRNG serves as a caretaker that can initialize the PRNG (originator) with the same seed (the memento) to produce an identical sequence of pseudorandom numbers.
How Does Memo Design Pattern Work?
To store and manage the internal state of an object, memo design patterns use three different class objects that are
- Originator: This object is responsible for creating a memo that can store and restore its internal state and decide which internal state memento object stores according to required needs.
- Memento: This object is used to store the internal state of the Originator and can prevent objects (other than Originator) from accessing this object.
- Caretaker: This object is responsible for storing the memo. Caretaker can neither access nor operate on contents of the memo and can only pass the memo to other objects.
How Memo Design Pattern Does Not Violate Encapsulation?
Encapsulation is wrapping up data in a single unit. It is also a protective shield that prevents the data from being accessed outside the code without the shield. A well-designed object is encapsulated, and the internal representations of the object (data structures) are hidden inside the object and cannot be accessed from outside from its object. When a memo design pattern has direct access to the object’s fields/getters/setters, it can violate objects encapsulation.
To solve this problem, only the originator object that creates a memento object is allowed to access it. The caretaker object can request a memento object from the originator (to save the internal state) and pass the memento object back to the originator (to restore the state). This way, the encapsulation properties of the object (originator) are not violated.
Using this approach enables to save and restore the internal state of the originator without violating its encapsulation.
In the above figure, Caretaker class refers to Originator class to save and restore Originator class internal state using function createMemento() and restore(memento). The Originator class implements two methods
- createMemento() creates and returns an object of type Memento that stores the originator's current states.
- restore(memento) function is used to restore state from the passed argument of type Memento.
This image shows the run-time interactions:
- Saving state: The Caretaker object is used to call the function createMemento() on Originators object to create a new object memento that saves the current state using function setState().
- Restoring: Caretaker class the function restore(memento) on Originators object and Memento object is passed as a function argument that stores state that should be restored. The Originator gets the state from memento using the function call getState().
Now that we know the three main components in the memo design pattern let us see how we can use these components to store object states.
Implementation Details
Step 1: Memento Class
A Memento class stores the detail of an object's internal state, which can be used later to restore an object to its previous state.
We have taken strong as our state, but any data type can be based on requirements.
Step 2: Originator class
In Originator class function restoreFromMemento(memento) is used to restore the object state from current state to a previous state using an object of type Memento passed in the function argument.
Step 3: CareTake Class
Here, mementoList is an array of Memento that is used to store several different states of the Originator object. This list is helpful to restore the Originator object to any required previous state.
Step 4: Use Classes to Save and Restore Object State
Let us use our three created classes to save and restore the states of an object.
Output
Here, we use variable careTaker that internally has an ArrayList of type Memento to store different states of object Originator. We first set the state of originator as Scaler and then save it using careTaker. Similarly, state Topics is saved, and we change the state again to State # 3. From the output, we can verify that the current state of the originator object is State # 3, and its first and second last states were Scaler and Topics, respectively.
Example: Undo
JAVA
C++
Python
Output
In this example, our state is a mutable string state, and in almost all real-life use cases state will have mutable objects.
Pros and Cons of Memo Design Pattern
The memo design pattern has several advantages and a few disadvantages as well. Let us discuss them in this section.
Advantages
- Memo design patterns provide a mechanism to restore object states with violating concepts of encapsulation. This provides the ability for the user to return to a particular previously obtained state.
- Using the memo design pattern helps in archiving or saving information (state/data members) of an object so that users don't need to care about storing it on their own.
- This design pattern simplifies code by maintaining the history of the originator's object.
Disadvantages
- Memo design pattern consume resources, and if the class has too many member variables, it will inevitably take up a relatively large resource. Saving each state consumes a certain amount of memory.
- This design pattern causes problems with languages that support dynamically typed languages as there is no guarantee that Memento objects will not be altered.
- It is not easy to delete an object of type Memento as the CareTaker needs to track the originator's life cycle to get the required result.
FAQs
Q. What is the difference between Memento and CareTaker Class?
A. The Memento class only has states (generally mutable), which are internal states on the Originator object. We create Memento objects when we want to store current internal states of Originator objects whereas, the CareTaker class is responsible for storing mementos just like a repository. CareTaker class does not make changes to mementos and only store them as a container.
Q. How are States Decided in the Memento Class?
A. The Memento class is used to restore Originator to a previously occurred state. For this reason, to restore the originator object, we need to have all the information the Originator object has. So, the Memento object has all the data members from the Originator class essential to store and restore them later in the program's lifecycle.
Q. Relationship with other Design Patterns
A.
- Memo design patterns can be used along with Command design pattern to implement the "undo" feature. Here, commands will be responsible for performing several actions on the target object, and mementos will save the object's state before a command is executed.
- Memento can be used along with Iterator to store the current iteration state and roll it back to previous states when required.
- Sometimes Prototype can be similar to Memo design pattern. This case happens when the target object has a reasonably straightforward state and does not have links to external resources, or the links are easy to restore.