The code is available on GitHub
The Memento Pattern allows us to restore the state of an object, creating this way undo operations or checkpoints in our application.
When to use
Use the Memento Pattern if you want to implement undo operations in your application.
Its very common for an application to have undo operations. Typically, this involves restoring the state of a specific class or module within the application. An important aspect we have to consider is to avoid exposing the internals of the object we want to restore, compromising the application’s reliability and extensibility. We can address this problem by using an object called memento, which stores a snapshot of the internal state of another object, called originator. Only the originator can store and retrieve information from the memento object.
Use the Memento Pattern if you want to retrieve the state of an object without compromising the object’s encapsulation.
By using the memento object instead of directly accessing an object’s state, we limit the access to the internal state of the object only to the memento object. Otherwise, if other parts are tightly coupled with the object’s state, any refactoring of the object would result in numerous changes, leading in a more fragile design.
How to implement
Define a Memento object to hold the state of the Originator object.
The Memento object can have methods like GetState() and SetState() to set its state, or we can use different methods for the properties we want to set. Below we show an example of a Memento object.
[Serializable]
public class Memento
{
private object state;
public Memento(object state)
{
this.state = state;
}
public object GetState()
{
return state;
}
}
In the above example, we mark the Memento object Serializable, which allows us to save and retrieve it from disk in a binary format.
You can also consider implementing an interface, IMemento, to further encapsulate the Memento’s methods from the rest of the application. The Memento class can implement the interface explicitly meaning that we can call its methods only as IMemento and not as Memento.
public interface IMemento
{
object GetState();
}
[Serializable]
public class Memento : IMemento
{
private readonly object _state;
public Memento(object state)
{
_state = state;
}
// Explicit implementation of the IMemento interface.
object IMemento.GetState() => _state;
}
Next, we define the Originator object that we want to restore to a previous state.
If you already have an Originator object in your application, add a SaveState() method to it to obtain a Memento containing the state you want to store. Also, add a Restore() method that accepts a Memento (or IMemento) parameter, from which the Originator object will restore its state.
Only the originator can instantiate the memento with information that characterizes its current state.
public class Originator
{
private readonly object _state;
public Memento SaveState()
{
return new Memento(_state);
}
public void Restore(IMemento memento)
{
board = memento.GetState();
}
}
By hiding the state inside the Memento and not allowing the rest of the application to directly modify it, you avoid unexpected behavior if some part of the application modifies the state that may hold the same references as those in the Originator. In case you want to copy the objects before passing them in the memento class, you can explore the Prototype Pattern to learn how to clone an object.
Finally, make use of the memento object from the Originator in order to save and restore its state.
We can call the SaveState() method of the originator at any point in our application’s lifecycle and store the memento object on disk or another medium. We can also save multiple memento objects along with a timestamp or enrich the memento to contain one if needed. Later, we can retrieve a memento object and pass it to the Restore method to restore the originator’s state. Here’s an example:
var originator = new Originator();
var memento1 = originator.SaveState();
SaveMementoToDisk("checkpoint1", memento1);
// Time passes.
var memento2 = originator.SaveState();
SaveMementoToDisk("checkpoint2", memento2);
var memento = GetMementoFromDisk("checkpoint2");
originator.RestoreState(memento);
In the above code, we create an originator object and save its state at two different checkpoints (memento1 and memento2). We store these Memento objects on disk using the SaveMementoToDisk() function. Later, we retrieve the desired Memento object from disk using the GetMementoFromDisk() function and restore the originator’s state by calling originator.Restore(memento).
Memento Pattern – Class diagram
Below we present the different components of the Memento Pattern. The client retrieves a Memento object, which can be stored and used later to restore the state of the Originator object.
Real World Example
The Memento Pattern is used in various applications, including games that utilize the pattern to save and restore the game state. Also, the WP Rollback plugin in WordPress uses the Memento Pattern to rollback any theme or plugin from WordPress to any previous (or newer) version without any of the manual work required.
Example – Save and Restore State of a Tic-tac-toe game
In this section, we demonstrate how to save and restore the state for a Tic Tac Toe game. The originator in our example is the TicTacToeBoard class, which also holds an 3×3 char array representing the game state. The array is part of the TicTacToeBoard’s internal state and will be private. Only the Memento object will have access to the TicTacToeBoard’s state and will be used for saving and restoring its state.
The TicTacToeBoard class will have methods for creating and restoring a Memento, as well as other methods related to the game itself. For example, the MakeMove() method marks an array item with ‘X’ or ‘O’, and the CheckForWin() method checks if there is a winner based on the current state of the board.
Below we present the TicTacToeBoard ( originator ) class:
public class TicTacToeBoard
{
private char[,] board;
public TicTacToeBoard()
{
// Initialize the board state.
board = new char[3, 3]
{
{ ' ', ' ', ' ' },
{ ' ', ' ', ' ' },
{ ' ', ' ', ' ' }
};
}
public void MakeMove(int row, int col, char player)
{
board[row, col] = player;
}
public void DisplayBoard()
{
Console.WriteLine("-------------");
for (int i = 0; i < 3; i++)
{
Console.Write("| ");
for (int j = 0; j < 3; j++)
{
Console.Write(string.Format("{0,-1} | ", board[i, j]));
}
Console.WriteLine();
Console.WriteLine("-------------");
}
Console.WriteLine();
}
public Memento Save()
{
char[,] state = new char[3, 3];
Array.Copy(board, state, board.Length);
return new Memento(state); // Instantiate a new Memento object
// with the internal state of the TicTacToeBoard.
}
public void Restore(IMemento memento)
{
board = memento.GetBoard(); // Set the current state with the state
// contained inside the memento object.
}
public char CheckForWin()
{
// Check rows.
for (int row = 0; row < 3; row++)
{
if (board[row, 0] != '\0' && board[row, 0] == board[row, 1] && board[row, 1] == board[row, 2])
{
return board[row, 0];
}
}
// Check columns.
for (int col = 0; col < 3; col++)
{
if (board[0, col] != '\0' && board[0, col] == board[1, col] && board[1, col] == board[2, col])
{
return board[0, col];
}
}
// Check diagonals.
if (board[0, 0] != '\0' && board[0, 0] == board[1, 1] && board[1, 1] == board[2, 2])
{
return board[0, 0];
}
if (board[0, 2] != '\0' && board[0, 2] == board[1, 1] && board[1, 1] == board[2, 0])
{
return board[0, 2];
}
return '\0';
}
}
In the above code, the Save() method creates a Memento object by passing the array to its constructor. This way, the Memento can store the internal state of the Originator. We can have as many different states of the game as we like. Additionally, the Restore() method accepts an IMemento object to restore the state of the TicTacToeBoard. Notice that we pass an interface ( IMemento ) rather than a concrete object of the Memento. This adds a layer of isolation, preventing direct access and modification of the Memento’s state from other components of the application. The IMemento interface has a single method, GetBoard(), which returns the state of the board as show below:
public interface IMemento
{
char[,] GetBoard();
}
The Memento class implements the IMemento interface explicitly, ensuring that external components cannot access the Memento’s state as a concrete class. Only the Originator, which receives the IMemento interface as a parameter in its Restore() method, can access the GetBoard() method.
[Serializable]
public class Memento : IMemento
{
private readonly char[,] _board;
public Memento(char[,] board)
{
_board = board;
}
// Explicit implementation of the IMemento interface.
char[,] IMemento.GetBoard() => _board;
}
Furthermore, we mark the Memento as Serializable to enable serialization and deserialization of the object in a binary format for storage purposes.
Tic-tac-toe Memento Class Diagram
Here is the class diagram for the Tic-tac-toe game using the Memento Pattern:
The different components in the diagram are explained below:
- Memento: Represents the concrete memento object that stores the internal state of the TicTacToeBoard.
- IMemento: An interface that acts as an isolator between the Memento and the rest of the application, providing access to the board state.
- TicTacToeBoard: The originator class that holds the game state and provides methods for making moves, saving and restoring the state, and checking for a win.
Creating the Gameplay
We can create the gameplay using a simple loop that reads commands from the user. The following commands are available:
- print – Displays the current state of the board using ASCII characters.
- save – Calls the Save method of the TicTacToeBoard to obtain a Memento object to store.
- restore – Restores the state of the board to a previous one.
- {row},{column},{playerCharater} – A player can make a move by marking a specific cell of the board with a character (‘X’ or ‘O’), e.g. 0,0,X.
Here is an example of the loop structure:
TicTacToeBoard game = new TicTacToeBoard();
while(true)
{
string command = Console.ReadLine();
if (command == "print")
{
game.DisplayBoard();
}else if(command == "save")
{
var memento = game.Save();
SerializeMemento(memento, "memento.bin");
}else if(command == "restore")
{
var memento = DeserializeMemento("memento.bin");
game.Restore(memento);
game.DisplayBoard();
}
else
{
int x = Convert.ToInt32(command.Split(',')[0]);
int y = Convert.ToInt32(command.Split(',')[1]);
var player = command.Split(',')[2][0];
game.MakeMove(x, y, player);
game.DisplayBoard();
var winner = game.CheckForWin();
if (winner != '\0')
Console.WriteLine("Game over! Winner is " + winner);
}
}
In addition to the previous commands, every time a player makes a move, we call the CheckForWin() method to determine if there is a winner or not.
The Serialize/Deserialize methods along with the rest of the code can be found on Github.
Usage Example
In this section, we provide an example game with player moves, multiple game saves, and restoring the game’s state.
1,1,X // X Player move
-------------
| | | |
-------------
| | X | |
-------------
| | | |
-------------
0,0,O // O Player move
-------------
| O | | |
-------------
| | X | |
-------------
| | | |
-------------
2,0,X // X Player move
-------------
| O | | |
-------------
| | X | |
-------------
| X | | |
-------------
save // In this point we save the game's state.
0,2,O // O Player move
-------------
| O | | O |
-------------
| | X | |
-------------
| X | | |
-------------
0,1,X // X Player move.
-------------
| O | X | O |
-------------
| | X | |
-------------
| X | | |
-------------
restore // We restore the game's state.
-------------
| O | | |
-------------
| | X | |
-------------
| X | | |
-------------
Using Dependency Injection
Not Applicable.
Similar Patterns
Commands can utilize mementos to maintain state for undoable operations.
Iterator can use the Memento Pattern in order to pause and resume iteration.
Further Reading
Looking to dive deeper into design patterns and take your programming skills to the next level? Check out the following highly recommended books:
* This site contains product affiliate links. We may receive a commission if you make a purchase after clicking on one of these links.