Implementation

To implement the Memento design pattern and show its advantages, we will work further on the Command design pattern’s example. So if you have missed the previous article, I strongly recommend checking the implementation part of it now.

The main idea of the example remains the same — we will create a very simple, fake graphics editor. To simplify the Command design pattern’s part, only one command is created and available in the example’s UI — RandomisePropertiesCommand. This command randomises all the properties of the Shape object — height, width and colour — which acts as a state of our example.

Obviously, what is different from the previous implementation — the Memento design pattern is added. When implementing the Command design pattern’s example, we stored its state (Shape object) in the example component itself. This time, the state is stored inside the Originator object and could be manipulated only by it. The RandomisePropertiesCommand acts as a caretaker object and stores the previous snapshot of the originator’s state in the backup property. The backup property is nothing else than the Memento object which is created by the originator before executing the command.

As a result of using the Memento design pattern, the example’s state is encapsulated and moved outside from the example component. Also, the previous state could be restored from its Memento snapshot on undo() operation of the command. In this case, the Memento design pattern extends the Command design pattern and collaborates with it really well.

Before implementing the Memento design pattern and integrating it inside our example, let’s check the class diagram first and investigate its components.

Class diagram

The class diagram below shows the implementation of the Memento design pattern:

Class Diagram — Implementation of the Memento design pattern

ICommand is an abstract class which is used as an interface for the specific command:

execute() — an abstract method which executes the command;

undo() — an abstract method which undoes the command and returns the state to the previous snapshot of it.

RandomisePropertiesCommand is a concrete command which implements the abstract class ICommand and its methods.

CommandHistory is a simple class which stores a list of already executed commands (commandList) and provides methods to add a new command to the command history list (add()) and undo the last command from that list (undo()).

IMemento is an abstract class which is used as an interface for the specific memento class:

getState() — an abstract method which returns the snapshot of the internal originator’s state.

Memento is a class that acts as a snapshot of the originator’s internal state which is stored in the state property and returned via the getState() method.

Shape is a simple data class which is used as an internal originator’s state. It stores multiple properties defining the shape presented in UI: color, height and width.

Originator — a simple class which contains its internal state and stores the snapshot of it to the Memento object using the createMemento() method. Also, the originator’s state could be restored from the provided Memento object using the restore() method.

MementoExample initializes and contains CommandHistory, Originator objects. Also, this component contains a PlatformButton widget which has the command of RandomisePropertiesCommand assigned to it. When the button is pressed, the command is executed and added to the command history list stored in CommandHistory object.

Shape

A simple class to store information about the shape: its color, height and width. Also, this class contains several constructors:

Shape() — a basic constructor to create a shape object with provided values;

Shape.initial() — a named constructor to create a shape object with pre-defined initial values;

Shape.copy() — a named constructor to create a shape object as a copy of the provided Shape value.

ICommand

An interface which defines methods to be implemented by the specific command classes. Dart language does not support the interface as a class type, so we define an interface by creating an abstract class and providing a method header (name, return type, parameters) without the default implementation.

RandomisePropertiesCommand

A specific implementation of the command which sets all the properties of the Shape object stored in the Originator to random values. Also, the class implements the undo operation.

CommandHistory

A simple class which stores a list of already executed commands. Also, this class provides isEmpty getter method to return true if the command history list is empty. A new command could be added to the command history list via the add() method and the last command could be undone using the undo() method (if the command history list is not empty).

IMemento

An interface which defines the getState() method to be implemented by the specific Memento class.

Memento

An implementation of the IMemento interface which stores the snapshot of Originator’s internal state (Shape object). The state is accessible to the Originator via the getState() method.

Originator

A class which defines a createMemento() method to save the current internal state to a Memento object.

Example

First of all, a markdown file is prepared and provided as a pattern’s description:

MementoExample contains CommandHistory and Originator objects. Also, this widget contains a PlatformButton component which uses the RandomisePropertiesCommand to randomise property values of the shape. After the command’s execution, it is added to the command history list stored in the CommandHistory object. If the command history is not empty, the Undo button is enabled and the last command could be undone.

As you can see in this example, the client code (UI elements, command history, etc.) isn’t coupled to any specific command class because it works with it via the ICommand interface.

In addition to what the Command design pattern provides to this example, the Memento design pattern adds an additional layer on the example’s state. It is stored inside the Originator object, the command itself does not mutate the state directly but through the Originator. Also, the backup (state’s snapshot) stored inside the Command is a Memento object and not the state (Shape object) itself — in case of the state’s restore (undo is triggered on the command), the specific command calls the restore method on the Originator which restores its internal state to the value stored in the snapshot. Hence, it allows restoring multiple property values (a whole complex state object) in a single request, while the state itself is completely separated from the command’s code or UI logic.

As you can see in the example, when the command is executed, under the hood the snapshot of originator’s internal state is stored which could be restored later by executing the undo operation on the command.

All of the code changes for the Memento design pattern and its example implementation could be found here.