Skip to content

Memento Design Pattern

Video Lecture

Section Video Links
Memento Pattern Memento Memento Pattern  
Memento Use Case Memento Use Case Memento Use Case  

Overview

Throughout the lifecycle of an application, an objects state may change. You might want to store a copy of the current state in case of later retrieval. E.g., when writing a document, you may want to auto save the current state every 10 minutes. Or you have a game, and you want to save the current position of your player in the level, with its score and current inventory.

You can use the Memento pattern for saving a copy of state and for later retrieval if necessary.

The Memento pattern, like the Command pattern, is also commonly used for implementing UNDO/REDO functionality within your application.

The difference between the Command and the Memento patterns for UNDO/REDO, is that in the Command pattern, you re-execute commands in the same order that changed attributes of a state, and with the Memento, you completely replace the state by retrieving from a cache/store.

Terminology

  • Originator: The originator is an object with an internal state that changes on occasion.
  • Caretaker: (Guardian) A Class that asks the Originator to create or restore Mementos. The Caretaker than saves them into a cache or store of mementos.
  • Memento: A copy of the internal state of the Originator that can later be restored back into the Originator to replace its current state.

Memento UML Diagram

Memento UML Diagram

Source Code

In the concept code, the client creates an object whose state will be periodically recorded. The object will be the Originator.

A Caretaker is also created with a reference to the Originator.

The Originators internal state is changed several times. It is then decided that the Caretaker should make a backup.

More changes are made to the Originator, and then another backup is made.

More changes are made to the Originator, and then it is decided that the first backup should be restored instead.

And then the second backup is restored.

./src/memento/memento-concept.ts

 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
90
91
92
93
94
95
96
97
98
99
'Memento pattern concept'

class Memento {
    // A container of state
    state: string
    constructor(state: string) {
        this.state = state
    }
}

class Originator {
    // The Object in the application whose state changes

    #state: string

    constructor() {
        this.#state = ''
    }

    public get state(): string {
        return this.#state
    }

    public set state(value: string) {
        this.#state = value
        console.log(`Originator: Set state to '${value}'`)
    }

    public get memento(): Memento {
        console.log(
            'Originator: Providing Memento of state to caretaker.'
        )
        return new Memento(this.#state)
    }

    public set memento(value: Memento) {
        this.#state = value.state
        console.log(
            `Originator: State after restoring from Memento: '${
                this.#state
            }'`
        )
    }
}

class CareTaker {
    // Guardian. Provides a narrow interface to the mementos

    #originator: Originator
    #mementos: Memento[]

    constructor(originator: Originator) {
        this.#originator = originator
        this.#mementos = []
    }

    create() {
        // Store a new Memento of the Originators current state
        console.log(
            'CareTaker: Getting a copy of Originators current state'
        )
        const memento = this.#originator.memento
        this.#mementos.push(memento)
    }

    restore(index: number) {
        // Replace the Originators current state with the state stored in the saved Memento
        console.log('CareTaker: Restoring Originators state from Memento')
        const memento = this.#mementos[index]
        this.#originator.memento = memento
    }
}

// The Client
const ORIGINATOR = new Originator()
const CARETAKER = new 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'
console.log(ORIGINATOR.state)

// restore from first backup
CARETAKER.restore(0)
console.log(ORIGINATOR.state)

// restore from second backup
CARETAKER.restore(1)
console.log(ORIGINATOR.state)

Output

node ./dist/memento/memento-concept.js
Originator: Set state to 'State #1'
Originator: Set state to 'State #2'
CareTaker: Getting a copy of Originators current state
Originator: Providing Memento of state to caretaker.
Originator: Set state to 'State #3'
CareTaker: Getting a copy of Originators current state
Originator: Providing Memento of state to caretaker.
Originator: Set 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

Memento Use Case

There is a game, and the character is progressing through the levels. It has acquired several new items in its inventory, the score is very good, and you want to save your progress and continue later.

You then decide you made a mistake and need to go back to a previous save because you took a wrong turn.

Example UML Diagram

Memento Use Case UML Diagram

Source Code

./src/memento/client.ts

 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

import CareTaker from './caretaker'
import GameCharacter from './game-character'

const GAME_CHARACTER = new GameCharacter()
const CARETAKER = new CareTaker(GAME_CHARACTER)

// start the game
GAME_CHARACTER.registerKill()
GAME_CHARACTER.moveForward(1)
GAME_CHARACTER.addInventory('sword')
GAME_CHARACTER.registerKill()
GAME_CHARACTER.addInventory('rifle')
GAME_CHARACTER.moveForward(1)
console.log(GAME_CHARACTER.status())

// save progress
CARETAKER.save()

GAME_CHARACTER.registerKill()
GAME_CHARACTER.moveForward(1)
GAME_CHARACTER.progressToNextLevel()
GAME_CHARACTER.registerKill()
GAME_CHARACTER.addInventory('motorbike')
GAME_CHARACTER.moveForward(10)
GAME_CHARACTER.registerKill()
console.log(GAME_CHARACTER.status())

// save progress
CARETAKER.save()
GAME_CHARACTER.moveForward(1)
GAME_CHARACTER.progressToNextLevel()
GAME_CHARACTER.registerKill()
console.log(GAME_CHARACTER.status())

// decide you made a mistake, go back to first save
CARETAKER.restore(0)
console.log(GAME_CHARACTER.status())

// continue
GAME_CHARACTER.registerKill()

./src/memento/game-character.ts

 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
// The Game Character whose state changes

import Memento from './memento'

export default class GameCharacter {
    #score: number
    #inventory: Set<string>
    #level: number
    #location: { x: number; y: number; z: number }

    constructor() {
        this.#score = 0
        this.#inventory = new Set()
        this.#level = 0
        this.#location = { x: 0, y: 0, z: 0 }
    }

    public get score(): number {
        // A getter for the score"
        return this.#score
    }

    registerKill(): void {
        // The character kills its enemies as it progresses
        this.#score += 100
    }

    addInventory(item: string): void {
        // The character finds objects in the game
        this.#inventory.add(item)
    }

    progressToNextLevel(): void {
        // The character progresses to the next level
        this.#level = this.#level + 1
    }

    moveForward(amount: number): void {
        // The character moves around the environment
        this.#location['z'] += amount
    }

    status(): string {
        return (
            `Score: ${this.#score}, ` +
            `Level: ${this.#level}, ` +
            `Location: ${JSON.stringify(this.#location)}\n` +
            `Inventory: ${JSON.stringify(Array.from(this.#inventory))}`
        )
    }

    public get memento(): Memento {
        'A `getter` for the characters attributes as a Memento'
        return new Memento(
            this.#score,
            new Set(this.#inventory),
            this.#level,
            Object.assign({}, this.#location)
        )
    }

    public set memento(value: Memento) {
        this.#score = value.score
        this.#inventory = value.inventory
        this.#level = value.level
        this.#location = value.location
    }
}

./src/memento/caretaker.ts

 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
// The Save/Restore Game functionality

import GameCharacter from './game-character'
import Memento from './memento'

export default class CareTaker {
    // Guardian. Provides a narrow interface to the mementos

    #originator: GameCharacter
    #mementos: Memento[]

    constructor(originator: GameCharacter) {
        this.#originator = originator
        this.#mementos = []
    }

    save(): void {
        // Store a new Memento of the Characters current state
        console.log('CareTaker: Game Save')
        const memento = this.#originator.memento
        this.#mementos.push(memento)
    }

    restore(index: number): void {
        // Replace the Characters current attributes with the state
        // stored in the saved Memento
        console.log(
            'CareTaker: Restoring Characters attributes from Memento'
        )
        const memento = this.#mementos[index]
        this.#originator.memento = memento
    }
}

./src/memento/memento.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// A Memento to store character attributes

export default class Memento {
    score: number
    inventory: Set<string>
    level: number
    location: { x: number; y: number; z: number }

    constructor(
        score: number,
        inventory: Set<string>,
        level: number,
        location: { x: number; y: number; z: number }
    ) {
        this.score = score
        this.inventory = inventory
        this.level = level
        this.location = location
    }
}

Output

node ./dist/memento/client.js
Score: 200, Level: 0, Location: {"x":0,"y":0,"z":2}
Inventory: ["sword","rifle"]
CareTaker: Game Save
Score: 500, Level: 1, Location: {"x":0,"y":0,"z":13}
Inventory: ["sword","rifle","motorbike"]
CareTaker: Game Save
Score: 600, Level: 2, Location: {"x":0,"y":0,"z":14}
Inventory: ["sword","rifle","motorbike"]
CareTaker: Restoring Characters attributes from Memento
Score: 200, Level: 0, Location: {"x":0,"y":0,"z":2}
Inventory: ["sword","rifle"]

Summary

  • You don't need to create a new Memento each time an Originators state changes. You can do it only when considered necessary. E.g., an occasional backup to a file.

  • Mementos can be stored in memory or saved/cached externally. The Caretaker will abstract the complications of storing and retrieving Mementos from the Originator.

  • Consider the Command pattern for fine-grained changes to an objects state to manage UNDO/REDO between memento saves. Or even save command history into a Memento that can be later replayed.

  • In my examples, the whole state is recorded and changed with the Memento. You can use the Memento to record and change partial states instead if required.

  • When copying state, be aware of shallow/deep copying. In complicated projects, your restore functionality will probably contain a combination of both the Command and Memento patterns.