Skip to content

Memento Design Pattern

Video Lecture

Section Video Links
Memento Overview Memento Overview Memento Overview Memento Overview 
Memento Use Case Memento Use Case Memento Use Case Memento Use Case 
Getters/Setters Getters/Setters Getters/Setters Getters/Setters 

Overview

...Refer to Book or Videos for extra content.

Terminology

...Refer to Book or Videos for extra content.

Memento UML Diagram

Memento UML Diagram

Source Code

...Refer to Book or Videos for extra content.

./memento/memento_concept.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"Memento pattern concept"


class Memento():  # pylint: disable=too-few-public-methods
    "A container of state"

    def __init__(self, state):
        self.state = state


class Originator():
    "The Object in the application whose state changes"

    def __init__(self):
        self._state = ""

    @property
    def state(self):
        "A `getter` for the objects state"
        return self._state

    @state.setter
    def state(self, state):
        print(f"Originator: Setting state to `{state}`")
        self._state = state

    @property
    def memento(self):
        "A `getter` for the objects state but packaged as a Memento"
        print("Originator: Providing Memento of state to caretaker.")
        return Memento(self._state)

    @memento.setter
    def memento(self, memento):
        self._state = memento.state
        print(
            f"Originator: State after restoring from Memento: "
            f"`{self._state}`")


class CareTaker():
    "Guardian. Provides a narrow interface to the mementos"

    def __init__(self, originator):
        self._originator = originator
        self._mementos = []

    def create(self):
        "Store a new Memento of the Originators current state"
        print("CareTaker: Getting a copy of Originators current state")
        memento = self._originator.memento
        self._mementos.append(memento)

    def restore(self, index):
        """
        Replace the Originators current state with the state
        stored in the saved Memento
        """
        print("CareTaker: Restoring Originators state from Memento")
        memento = self._mementos[index]
        self._originator.memento = memento


# The Client
ORIGINATOR = Originator()
CARETAKER = CareTaker(ORIGINATOR)

# originators state can change periodically due to application events
ORIGINATOR.state = "State #1"
ORIGINATOR.state = "State #2"

# lets backup the originators
CARETAKER.create()

# more changes, and then another backup
ORIGINATOR.state = "State #3"
CARETAKER.create()

# more changes
ORIGINATOR.state = "State #4"
print(ORIGINATOR.state)

# restore from first backup
CARETAKER.restore(0)
print(ORIGINATOR.state)

# restore from second backup
CARETAKER.restore(1)
print(ORIGINATOR.state)

Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
python ./memento/memento_concept.py
Originator: Setting state to `State #1`
Originator: Setting state to `State #2`
CareTaker: Getting a copy of Originators current state
Originator: Providing Memento of state to caretaker.
Originator: Setting state to `State #3`
CareTaker: Getting a copy of Originators current state
Originator: Providing Memento of state to caretaker.
Originator: Setting state to `State #4`
State #4
CareTaker: Restoring Originators state from Memento
Originator: State after restoring from Memento: `State #2`
State #2
CareTaker: Restoring Originators state from Memento
Originator: State after restoring from Memento: `State #3`
State #3

Example Use Case

...Refer to Book or Videos for extra content.

Example UML Diagram

Memento Use Case UML Diagram

Source Code

./memento/client.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
"Memento example Use Case"

from game_character import GameCharacter
from caretaker import CareTaker

GAME_CHARACTER = GameCharacter()
CARETAKER = CareTaker(GAME_CHARACTER)

# start the game
GAME_CHARACTER.register_kill()
GAME_CHARACTER.move_forward(1)
GAME_CHARACTER.add_inventory("sword")
GAME_CHARACTER.register_kill()
GAME_CHARACTER.add_inventory("rifle")
GAME_CHARACTER.move_forward(1)
print(GAME_CHARACTER)

# save progress
CARETAKER.save()

GAME_CHARACTER.register_kill()
GAME_CHARACTER.move_forward(1)
GAME_CHARACTER.progress_to_next_level()
GAME_CHARACTER.register_kill()
GAME_CHARACTER.add_inventory("motorbike")
GAME_CHARACTER.move_forward(10)
GAME_CHARACTER.register_kill()
print(GAME_CHARACTER)

# save progress
CARETAKER.save()
GAME_CHARACTER.move_forward(1)
GAME_CHARACTER.progress_to_next_level()
GAME_CHARACTER.register_kill()
print(GAME_CHARACTER)

# decide you made a mistake, go back to first save
CARETAKER.restore(0)
print(GAME_CHARACTER)

# continue
GAME_CHARACTER.register_kill()

./memento/game_character.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
"The Game Character whose state changes"
from memento import Memento

class GameCharacter():
    "The Game Character whose state changes"

    def __init__(self):
        self._score = 0
        self._inventory = set()
        self._level = 0
        self._location = {"x": 0, "y": 0, "z": 0}

    @property
    def score(self):
        "A `getter` for the objects score"
        return self._score

    def register_kill(self):
        "The character kills its enemies as it progesses"
        self._score += 100

    def add_inventory(self, item):
        "The character finds objects in the game"
        self._inventory.add(item)

    def progress_to_next_level(self):
        "The characer progresses to the next level"
        self._level += 1

    def move_forward(self, amount):
        "The character moves around the environment"
        self._location["z"] += amount

    def __str__(self):
        return(
            f"Score: {self._score}, "
            f"Level: {self._level}, "
            f"Location: {self._location}\n"
            f"Inventory: {self._inventory}\n"
        )

    @ property
    def memento(self):
        "A `getter` for the characters attributes as a Memento"
        return Memento(
            self._score,
            self._inventory.copy(),
            self._level,
            self._location.copy())

    @ memento.setter
    def memento(self, memento):
        self._score = memento.score
        self._inventory = memento.inventory
        self._level = memento.level
        self._location = memento.location

./memento/caretaker.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"The Save/Restore Game functionality"

class CareTaker():
    "Guardian. Provides a narrow interface to the mementos"

    def __init__(self, originator):
        self._originator = originator
        self._mementos = []

    def save(self):
        "Store a new Memento of the Characters current state"
        print("CareTaker: Game Save")
        memento = self._originator.memento
        self._mementos.append(memento)

    def restore(self, index):
        """
        Replace the Characters current attributes with the state
        stored in the saved Memento
        """
        print("CareTaker: Restoring Characters attributes from Memento")
        memento = self._mementos[index]
        self._originator.memento = memento

./memento/memento.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
"A Memento to store character attributes"

class Memento():  # pylint: disable=too-few-public-methods
    "A container of characters attributes"

    def __init__(self, score, inventory, level, location):
        self.score = score
        self.inventory = inventory
        self.level = level
        self.location = location

Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
python ./memento/client.py
Score: 200, Level: 0, Location: {'x': 0, 'y': 0, 'z': 2}
Inventory: {'rifle', 'sword'}

CareTaker: Game Save
Score: 500, Level: 1, Location: {'x': 0, 'y': 0, 'z': 13}
Inventory: {'motorbike', 'rifle', 'sword'}

CareTaker: Game Save
Score: 600, Level: 2, Location: {'x': 0, 'y': 0, 'z': 14}
Inventory: {'motorbike', 'rifle', 'sword'}

CareTaker: Restoring Characters attributes from Memento
Score: 200, Level: 0, Location: {'x': 0, 'y': 0, 'z': 2}
Inventory: {'rifle', 'sword'}

New Coding Concepts

Python Getter/Setters

Often when coding attributes in classes, you may want to provide methods to allow external functions to read or modify a classes internal attributes.

A common approach would be to add two methods prefixed with get_ and set_,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class ExampleClass:
    def __init__(self):
        self._value = 123

    def get_value(self):
        return self._value

    def set_value(self, value):
        self._value = value

example = ExampleClass()
print(example.get_value())

This makes perfect sense what the intentions are, but there is a more pythonic way of doing this and that is by using the inbuilt Python @property decorator.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class ExampleClass:
    def __init__(self):
        self._value = 123

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

example = ExampleClass()
print(example.value)

Note that in the above example, there is an extra decorator named @value.setter . This is used for setting the _value attribute.

Along with the above two new getter/setter methods, there is also another method for deleting an attribute called deleter .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class ExampleClass:
    def __init__(self):
        self._value = 123

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        self._value = value

    @value.deleter
    def value(self):
        print('Deleting _value')
        del self._value

example = ExampleClass()
print(example.value)
del example.value
print(example.value) # now raises an AttributeError

Summary

...Refer to Book or Videos for extra content.