This week we’ll continue our exploration of design patterns and look at another behavioural pattern, the Memento pattern.
The Gang of Four book defines the Memento pattern as follows:
“Without violating encapsulation, capture and externalise an object’s internal state so that it can be restored to this state later.”
The Memento pattern provides an undo mechanism for an application when the internal state of an object possibly needs to be restored at a later stage. A simple example is that of a text editor where we can save text, and undo any changes to restore the text to its previous state. Rolling back a database transaction is another example.
The Problem
Occasionally we might need to save the internal state of an object in order to implement a restore point or an undo operation. We will have to save this state somewhere, so that we can restore the previous state of the object at a later stage.
An object usually encapsulates and hides its internal state. This state is then inaccessible to other objects, and cannot be saved by an external object. We could expose the state by making the internal fields public, but this violates encapsulation and compromises the object’s integrity.
We also want to conform to the Single Responsibility principle, so we don’t want the class to have its own save-and-restore mechanism. So how do we capture the state of an object, and possibly restore the object to a previous state?
The Solution: Memento Pattern
We solve this problem with the Memento pattern. The pattern is implemented with three objects: the originator, the memento and the caretaker:
- The originator is some object that has an internal state.
- The memento is an object that stores a snapshot of the internal state of the originator. The memento object is an opaque object, i.e., it can’t be read by other objects.
- The caretaker is the client code that includes the undo functionality.
The sequence is as follows:
- The caretaker wants to perform an operation using the originator, but wants to be able to undo any changes if necessary.
- The caretaker asks the originator for a memento object when it needs to save the originator’s state.
- The originator creates the memento with its current state.
- The caretaker performs the operation on the originator.
- If the caretaker needs to undo the operation and return to the previous state, it returns the memento object to the originator.
- The originator uses the memento to restore its own state. Only the originator can store and retrieve information from the memento.
Ways to Implement the Memento Pattern
There are three fairly straightforward ways to implement the Memento pattern in Java:
- Serialization. It’s very easy to save and restore object state using serialization. This is more generic than the original Memento pattern where every object has to have its own memento class. A disadvantage of serialization is that if the originator object is large, then the serialized memento object will also be large.
- Packaging. We can declare a class in the same package that can access fields of the originator with default/package-private access. We have to tightly control which classes are contained in this package. Either the originator and the memento must be in a package by themselves, or other classes in the same package will have direct access to their instance variables.
- Proxies. The object can be accessed via a proxy that can implement save/restore operations on the object.
Memento Pattern: Example Code
Our first person shooter game is coming along nicely. We can create various monsters and creatures, engage in battles and calculate damage. We can request reinforcements. We can upgrade vehicles with more weapons and armour.
We have now decided that players need to be able to save a game at any point and continue playing at a later stage. They do need to sleep and go to work occasionally…
Let’s create a class with one method to save the state of the game, and another to restore it.
For this we need to save the state of the game. We could decide to use a human readable file format like XML or JSON, but we don’t want to make it easy for anyone to cheat by editing the saved game files. A much better choice would be to use a binary format. A good solution for this would be to use the default Java binary serialization mechanism implemented in the ObjectOutputStream
and
classes.ObjectInputStream
import java.io.*; public class GameMemento { // The player needs the option to change the filename private String fileName = "game.sav"; // Constructor public GameMemento(Game game) throws IOException { // call the save method save(game); } // Memento code public void save(Game game) throws IOException { // Serialize the current state of the game File gameFile = new File(fileName); ObjectOutputStream oos = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream(gameFile))); oos.writeObject(game); oos.close(); } public Game restore() throws IOException, ClassNotFoundException { // Deserialize the previous state of the game... File gameFile = new File(fileName); ObjectInputStream ois = new ObjectInputStream( new BufferedInputStream( new FileInputStream(gameFile))); Game game = (Game) ois.readObject(); ois.close(); return game; } } // end of class
The Game
class will contain a collection of Combatant
objects (players, monsters, vehicles, etc.).
import java.util.*; public class Game implements java.io.Serializable { // collection of combatants private ArrayListcombatants; public Game() { combatants = new ArrayList<>(); } // methods to initialize the game, add and remove combatants, etc. //... } // end of class
A Combatant
object will contain data about its current position, movement, speed, health, weapons, armour, etc. – everything that we would need to restore the game to a previously saved state.
public class Combatant implements java.io.Serializable { // data fields for position, movement, speed, health, weapons, armour, etc. // constructors, accessors, canonical methods, business methods, etc. } // end of class
Both the Game
and the Combatant
classes need to implement java.io.Serializable
, so that they can be serialized by the memento class.
Example code for the client (caretaker) to save and restore state follows:
// Creating and initializing the game Game game = new Game(); game.init(); // Game running... // Save the state of the game when required System.out.println("Saving the state of the game..."); GameMemento memento = new GameMemento(game); // State of the game is changed as players play... // Restore the game to the previous saved state System.out.println("Restoring game..."); game = memento.restore(); // Game runs from previously saved point...
Related Patterns
The Command pattern can use mementos to save state for any later undoable operations.
What’s Next?
In the weeks ahead, we’ll continue examining some of the more useful design patterns. Stay tuned!
As always, please share your comments and questions.