The memento pattern will be the last pattern that we will discover together. You will see that this pattern is really interesting to work with and has many uses.
The role of the memento pattern is to capture an object's internal state and save it externally so that it can be restored later without breaking the encapsulation of this object.
The following are the participants of the memento pattern:
Memento
: This is the class for the objects that saved the internal states of origin objects (or part of this state), such as the introduction of the fact that the saving of a state can be made independent of the object itself. The memento has two interfaces:Originator
objects that permit access to everything that needs to be saved or restoredOriginator
: This is the object class that creates memento to save their internal states and that they can restore from memento.caretaker
: This is responsible to manage the list of mementos and doesn't provide access to the internal state of the origin objects. This is also the class that the client code needs to access.CreateMemento
: This method is used to save the state of the originator. It creates a Memento
object by saving the state variable into the Memento
object and returns it. This is used to record the state of Originator
.SetMemento
: This class stores the historical information of the Originator
objects. The information is stored in its state variable.An instance of Caretaker
asks for a memento of the originator
object by calling the createMemento
method and saves it. In case of cancellation and going back to the saved state in the memento, you can send it to the originator
object using the setMemento
method.
You have developed a platform game where each time your hero passes a level, a checkpoint is stored. This allows the player to reload from any level that he has already crossed.
The data that needs to be stored when saving the checkpoint are the level numbers, the current weapon, and the number of points.
To simplify our code, we will use an OS X command line project, where we will write all our code in the main.swift
file.
Open the MementoPattern.xcodeproj
file in Xcode and let's see how we have organized our code.
Firstly, we define our GameState
structure: data that needs to be saved are level
, weapon
, and points
:
struct GameState { var level: Int var weapon: String var points: Int }
Then we define our Originator
with the createMemento
and setMemento
methods:
protocol Originator { func createMemento() -> GameMemento func setMemento(memento: GameMemento) }
Then we implement our GameState Memento
: it contains all the information that CheckPoint
needs to restore the state.
First, we define three variables that will help us to store states:
entries
is a list of GameState
nextId
contains the next index of the entries
arraytotalPoints
is a variable that has the total points of each entryWe have a constructor where we can pass a CheckPoint
object as an argument. States of the checkpoint are assigned to the memento internal variables.
The apply()
method receives a checkpoint object in the argument. Then, we assign the current memento values to the checkpoint properties in order to restore the checkpoint state:
struct GameMemento { private let entries: [Int: GameState] private let nextId: Int private let totalPoints: Int init(checkPoint: CheckPoint){ self.entries = checkPoint.entries self.nextId = checkPoint.nextId self.totalPoints = checkPoint.totalPoints } func apply(checkPoint: CheckPoint) { print("Restoring a game state to a checkpoint...") checkPoint.nextId = nextId checkPoint.totalPoints = totalPoints checkPoint.entries = entries } }
We can now save and restore a memento. The next thing to be done is to create our originator: the CheckPoint
object. While playing the game, an entry will be added to the checkpoint entries list.
Just like in the Memento
object, we will define three variables. The difference being that we initialize them in this object:
Entries
: This is an array that will contain all entries.totalPoints
: This is an integer initialized to 0
and it will contain the total of each level points.nextId
: This is an integer that starts from 0
. It contains the value of the next index in the entries
array.We have four methods:
addGameStateEntry
: This method is used to add a new entry to our entries list. This will be called by the client every time a level of our game is complete.createMemento
: This method creates and return a memento object. The checkpoint itself is sent as an argument of the method.setMemento
: This method allows us to restore a Memento
object.printCheckPoint
: This method is here to easily see what the current state of the CheckPoint
object is.The CheckPoint
class has the following implementation:
class CheckPoint: Originator { private var entries: [Int: GameState] = [:] private var totalPoints: Int = 0 private var nextId: Int = 0 func addGameStateEntry(level: Int, weapon: String, points: Int) { let entry = GameState(level: level, weapon: weapon, points: points) entries[nextId++] = entry totalPoints += points } func createMemento() -> GameMemento { return GameMemento(checkPoint: self) } func setMemento(memento: GameMemento) { memento.apply(self) } func printCheckPoint() { print("Printing checkPoint....") entries.sort {$0.0 < $1.0 } .map { print("Level: ($0.1.level) Weapon: ($0.1.weapon) Points: ($0.1.points) ") } print("Total Points: (totalPoints) ") } }
You have probably seen the following statement:
entries.sort {$0.0 < $1.0 } .map { print("Level: ($0.1.level) Weapon: ($0.1.weapon) Points: ($0.1.points) ") }
This is how we can easily sort our array by index value. Then we can use the map
function, only to be able to execute the print statement on each entry of the array.
You can see more information about closures to the following URL at: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
Our pattern is now in place, let's have a look at how to use it:
let checkPoint = CheckPoint() checkPoint.addGameStateEntry(0, weapon: "gun", points: 1200) checkPoint.addGameStateEntry(1, weapon: "tommy gun", points: 2250) checkPoint.printCheckPoint() let memento = checkPoint.createMemento() checkPoint.addGameStateEntry(2, weapon: "bazooka", points: 2400) checkPoint.addGameStateEntry(4, weapon: "knife", points: 3000) checkPoint.printCheckPoint() checkPoint.setMemento(memento) checkPoint.printCheckPoint()
After initializing a checkpoint object, we add two entries to the checkpoint.
After calling the printCheckPoint
method of the checkpoint
object: we obtain the following result:
Printing checkPoint.... Level: 0 Weapon: gun Points: 1200 Level: 1 Weapon: tommy gun Points: 2250 Total Points: 3450
The printCheckPoint
method iterates over all the entries of the checkpoint object and displays the state of each one and the total points of the entries.
At this moment, we have still not created a Memento
object, so we cannot restore a previous state.
To create a memento, we only have to call the createMemento
method of the Originator
object (in our case the checkPoint
object):
let memento = checkPoint.createMemento()
This method returns a memento object that we assign to our memento constant, so that we can restore when needed.
We continue the game and pass two others levels successfully and print the current checkpoint state once more:
checkPoint.addGameStateEntry(2, weapon: "bazooka", points: 2400) checkPoint.addGameStateEntry(4, weapon: "knife", points: 3000) checkPoint.printCheckPoint()
The result is as follows:
Printing checkPoint.... Level: 0 Weapon: gun Points: 1200 Level: 1 Weapon: tommy gun Points: 2250 Level: 2 Weapon: bazooka Points: 2400 Level: 4 Weapon: knife Points: 3000 Total Points: 8850
The CheckPoint
now contains four entries in its array.
We want to restore our checkpoint state to the last saved state. To proceed, we only need to call the setMemento()
method of the checkpoint object with the memento object we previously have created and make a call to the printCheckPoint
method to display the result, as shown:
checkPoint.setMemento(memento) checkPoint.printCheckPoint()
The result of the printCheckPoint
call is the following:
Restoring a game state to a checkpoint... Printing checkPoint.... Level: 0 Weapon: gun Points: 1200 Level: 1 Weapon: tommy gun Points: 2250 Total Points: 3450
We can see that the checkpoint object has been restored to its previous state.
With this simple example, you should now have a good pattern to use and easily manage cancellation and restoration of an object.
The memento pattern is a pattern that is extensively used in scientific computing to save the state of long-running computations. As seen here, it may be used to save the state of play over a matter of hours or days. In the graphics toolkit, it may be used to preserve the state of a display while the objects are being moved around.