Memento¶
The Memento
pattern describes how to manage the saved states of an object. Imagine a game (e.g. running on a console) that has auto-save functionality in certain places. In this game you move your character around the world. When such a save occurs, the game "needs to know", e.g. what equipment your character currently has, which tasks in the game have been completed and which are in progress, whether to know which places have been visited and which have not yet been visited to correctly display world map.
When you enter the options menu you see that there is an option "Load previous save". After selecting this option you have been moved to the place of the previous save. Let's assume that at this point you can choose the same option again and be transferred to an even earlier checkpoint.
Design¶
How are saved game objects stored? Well, they can be stored anywhere, depending on the functionality needed (database, separate files, RAM), but they must keep a copy of a certain state. Of course, it must be possible to create such a copy of the state and then restore it.
All the elements described above constitute the implementation of the Memento
pattern, we need:
- an object representing the state of the application (or the state of a certain part)
- an object representing the saved state of the application
- the ability to create a save object from the original state
- the ability to restore the original state based on the saved
- an object that somehow manages the saved states
When copying states, most often we have to use the deep copy
. The Python assignment operator doesn't copy an object, it just creates another reference to it. We can use the copy
module and its methods to make a copy:
copy.copy(x)
- creates a shallow copycopy.deepcopy(x)
- makes a deep copy
Example¶
The example shows how to save a certain game. This state is kept in the GameState
class, while the GameStateSnapshot
class represents its record. The GameStateSnapshot
instance is created by creating a copy of the GameState
class fields. It is also possible to restore the state of the GameState
class based on the value in the GameStateSnapshot
instance (between the GameState
and GameStateSnapshot
objects it is possible to make a deep copy
both ways).
The object that manages the saved states is the GameStateManager
class, which allows you to save the game state and load only the previous state. For this, the stack portion of the list interface representing the stack operations (append
and pop
) is used.
import copy
class GameState:
def __init__(self, health=0, mana=0, items=None):
self._health = health
self._mana = mana
if items is None:
self._items = []
else:
self._items = items
def __str__(self):
return f"GameState[health={self._health}, mana={self._mana}, items={self._items}]"
@property
def health(self):
return self._health
@property
def mana(self):
return self._mana
@property
def items(self):
return self._items
def heal(self):
self._health = 100
def take_damage(self, damage):
self._health -= damage
def add_item(self, item):
self._items.append(item)
def lose_all_items(self):
self._items.clear()
def restore_from_snapshot(self, snapshot):
self._health = snapshot.health
self._mana = snapshot.mana
self._items = copy.deepcopy(snapshot.items)
class GameStateSnapshot:
def __init__(self, game_state):
self._health = game_state.health
self._mana = game_state.mana
self._items = copy.deepcopy(game_state.items)
@property
def health(self):
return self._health
@property
def mana(self):
return self._mana
@property
def items(self):
return self._items
class GameStateManager:
def __init__(self):
self._snapshots = []
def save_game(self, game_state):
self._snapshots.append(GameStateSnapshot(game_state))
def restore_previous_checkpoint(self):
return self._snapshots.pop()
def main():
game_state = GameState(100, 80)
game_state_manager = GameStateManager()
game_state_manager.save_game(game_state)
print(game_state)
game_state.add_item('Basic Sword')
game_state.take_damage(10)
game_state_manager.save_game(game_state)
print(game_state)
game_state.take_damage(50)
game_state.add_item('Shield')
game_state_manager.save_game(game_state)
print(game_state)
game_state_manager.restore_previous_checkpoint()
game_state_snapshot = game_state_manager.restore_previous_checkpoint()
game_state.restore_from_snapshot(game_state_snapshot)
print(game_state)
if __name__ == '__main__':
main()
przykładowy output:
GameState[health=100, mana=80, items=[]]
GameState[health=90, mana=80, items=['Basic Sword']]
GameState[health=40, mana=80, items=['Basic Sword', 'Shield']]
GameState[health=90, mana=80, items=['Basic Sword']]
NOTE: Storing multiple saved states in memory can significantly increase memory usage. In this case, it is worth considering additional use of the pattern Flyweight.