The memento pattern

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.

Roles

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.

Design

The generic UML class diagram is defined as the following:

Design

Participants

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:
    • A complete interface for Originator objects that permit access to everything that needs to be saved or restored
    • A narrow interface to the caretaker that can keep and pass on the memento references, but no more
  • Originator: 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.

Collaboration

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.

Illustration

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.

Implementation

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 array
  • totalPoints is a variable that has the total points of each entry

We 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.

Note

Use this pattern when:

  • An object's state must be saved to be restored later
  • It's undesirable to expose the state directly
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset