Memento


V této části se podíváme na návrhový vzor Memento. Tento návrhový vzor je o tom, že si například můžeme uložit stav nějakého objektu a díky tomu jej v budoucnu obnovit.

Proč Memento použít

Objekt nebo systém může postupem času procházet různými změnami. Pokud chceme tyto změny ukládat, tak máme více možností. Jednou z nich je použít návrhový vzor Command. Další může být použít návrhový vzor Memento, o kterém je tato část.

Narozdíl od návrhového vzoru Command, kde si ukládáme provedené operace, si při použití Mementa ukládáme aktuální stav systému nebo objektu.

Příklad - Uložení stavu objektu

Následující ukázka ukazuje třídu BankovniUcet, která reprezentuje bankovní účet. Pomocí metod vlozit a vybrat můžeme s bankovním účtem manipulovat. Tyto metody vracejí memento uchovávající stav účtu po provedení jejich operace, které si můžeme uschovat a později jej použít k obnovení účtu.

// Memento pro třídu BankovniUcet
class Memento {
    constructor(zustatek) {
        this.zustatek = zustatek;
    }
}

class BankovniUcet {
    constructor(zustatek = 0) {
        this.zustatek = zustatek;
    }

    vlozit(castka) {
        this.zustatek += castka;
        // po provedení operace se vrátí nové memento
        return new Memento(this.zustatek);
    }

    vybrat(castka) {
        this.zustatek -= castka;
        // po provedení operace se vrátí nové memento
        return new Memento(this.zustatek);
    }

    obnovit(memento) {
        this.zustatek = memento.zustatek;
    }

    toString() {
        return `Zůstatek: ${this.zustatek}`;
    }
}


const ucet = new BankovniUcet(150);
console.log(ucet.toString());

// když provedeme s objektem ucet nějakou operaci, tak se nám vrátí
// memento, pomocí kterého můžeme objekt v budoucnu obnovit
const m1 = ucet.vybrat(50);
const m2 = ucet.vlozit(25);

console.log(ucet.toString());

// obnovení stavu objektu ucet pomocí mementa m1
ucet.obnovit(m1);

console.log(ucet.toString());

Příklad - Implementace undo/redo funkcionality

Memento můžeme použít k implementaci undo/redo funkcionality. Následující ukázka přidává třídě BankovniUcet z minulého příkladu metody undo a redo. Pomocí těchto metod se můžeme vracet o operaci zpět nebo dopředu.

class Memento {
    constructor(zustatek) {
        this.zustatek = zustatek;
    }
}

class BankovniUcet {
    constructor(zustatek = 0) {
        this.zustatek = zustatek;
        // v tomto poli se ukládají vytvořená mementa
        this.zmeny = [new Memento(zustatek)];
        // tato vlastnost udává index mementa v poli zmeny,
        // které odpovídá aktuálnímu stavu bankovnímu účtu
        this.aktualni = 0;
    }

    vlozit(castka) {
        this.zustatek += castka;
        // vytvoří se nové memento a přidá se do pole zmeny
        let m = new Memento(this.zustatek);
        this.zmeny.push(m);
        this.aktualni++;
        // memento se vrací, pokud bychom si jej chtěli někde uložit
        return m;
    }

    vybrat(castka) {
        this.zustatek -= castka;
        // vytvoří se nové memento a přidá se do pole zmeny
        let m = new Memento(this.zustatek);
        this.zmeny.push(m);
        this.aktualni++;
        // memento se vrací, pokud bychom si jej chtěli někde uložit
        return m;
    }

    // metoda pro obnovení účtu podle předaného mementa
    obnovit(memento) {
        if (memento) {
            this.zustatek = memento.zustatek;
            this.zmeny.push(memento);
            this.aktualni = this.zmeny.length-1;
        }
    }

    // pomocí této metody se můžeme vrátit o operaci zpět
    undo() {
        if (this.aktualni > 0) {
            let m = this.zmeny[--this.aktualni];
            this.zustatek = m.zustatek;
            return m;
        }
        return null;
    }

    // pomocí této metody se můžeme posunout o operaci dopředu
    redo() {
        if (this.aktualni+1 < this.zmeny.length) {
            let m = this.zmeny[++this.aktualni];
            this.zustatek = m.zustatek;
            return m;
        }
        return null;
    }

    toString() {
        return `Zůstatek: ${this.zustatek}`;
    }
}


const ucet = new BankovniUcet(100);
ucet.vlozit(50);
ucet.vybrat(25);

console.log(ucet.toString());

// pomocí metod undo a redo se můžeme
// vracet o změnu zpět a vpřed
ucet.undo();
console.log("Undo 1: " + ucet.toString());
ucet.undo();
console.log("Undo 2: " + ucet.toString());
ucet.redo();
console.log("Redo: " + ucet.toString());