Skip to content

Memento Design Pattern

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.

./src/memento/memento-concept.ts

'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

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

Example UML Diagram

Memento Use Case UML Diagram

Source Code

./src/memento/client.ts

// 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

// 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

// 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

// 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

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