Observer


V této části si ukážeme návrhový vzor Observer. Jedná se o objekt, který si přeje být informován o provedených událostech v systému.

Proč Observer použít

U některých objektů chceme, aby byli informováni když se v našem systému něco stane. Například když se změní vlastnost nějakého objektu a tak dále, prostě cokoliv.

Objekt, který naslouchá událostem je Observer, a entita, která události generuje je Observable.

Příklad - Vytvoření události a reagování na ni

Následující ukázka ukazuje třídu Clovek, která spouští událost onemocnet. K této události můžeme nastavit funkce, které proběhnou po jejím spuštění. Událost je objekt třídy Event, která obsahuje metody subscribe, unsubscribe a fire. Metoda subscribe slouží pro přihlášení k odběru události, metoda unsubscribe k odhlášení odběru události a pomocí metody fire se událost spouští.

// Tato třída představuje událost (event)
class Event {
    constructor() {
        // v této mapě se ukládají funkce, které
        // se mají po spuštění eventu zavolat
        this.handlers = new Map();
        this.count = 0;
    }

    // tato metoda slouží pro přidání funkce,
    // která se zavolá po spuštění události
    subscribe(handler) {
        // přidání funkce do mapy handlers
        this.handlers.set(this.count, handler);
        // vrací se token (klíč pod kterým se funkce nachází v mapě handlers)
        // - tento token se později může použít k odstranění funkce
        return this.count++;
    }

    // metoda k odstranění funkce z mapy handlers podle předaného tokenu
    unsubscribe(idx) {
        // aby se nemuseli posouvat indexy, tak používáme mapu a je to rychlejší
        this.handlers.delete(idx);
    }

    // zavolání této metody spustí event
    // - sender = objekt, který event spustil
    // - args = argumenty (objekt s informacemi)
    fire(sender, args) {
        // všechny funkce v mapě handlers se zavolají
        this.handlers.forEach(v => v(sender, args));
    }
}

// argumenty pro metodu fire třídy Event
class OnemocnetArgs {
    constructor(adresa) {
        this.adresa = adresa;
    }
}

class Clovek {
    constructor(adresa) {
        this.adresa = adresa;
        // událost pro onemocnění člověka
        this.onemocnet = new Event();
    }

    nachladitSe() {
        // spuštění události (eventu)
        this.onemocnet.fire(this, new OnemocnetArgs(this.adresa));
    }
}


const clovek = new Clovek('135 Praha');

// přihlášení k odběru události když člověk chytí nemoc
const token = clovek.onemocnet.subscribe((odesilatel, args) => {
    console.log(`Doktor byl přivolán na adresu: ${args.adresa}`);
});

clovek.nachladitSe();
clovek.nachladitSe();

// odhlášení odběru událostí pomocí získaného tokenu
clovek.onemocnet.unsubscribe(token);

clovek.nachladitSe();

Příklad - Pozorování změny vlastnosti objektu

Následující ukázka ukazuje, jak můžeme pozorovat změnu vlastnosti nějakého objektu, aniž by nás o tom sledovaný objekt přímo informoval. V ukázce je použita třída Event, která byla okomentována v minulém příkladu.

class Event {
    constructor() {
        this.handlers = new Map();
        this.count = 0;
    }

    subscribe(handler) {
        this.handlers.set(this.count, handler);
        return this.count++;
    }

    unsubscribe(idx) {
        this.handlers.delete(idx);
    }

    fire(sender, args) {
        this.handlers.forEach(v => v(sender, args));
    }
}

// argumenty pro metodu fire třídy Event
class ZmenaVlastnostiArgs {
    constructor(nazev, novaHodnota) {
        this.nazev = nazev;
        this.novaHodnota = novaHodnota;
    }
}

class Clovek {
    constructor(vek) {
        this._vek = vek;
        // událost pro změny vlastnosti vek
        this.zmenaVlastnosti = new Event();
    }

    get vek() { return this._vek; }

    set vek(hodnota) {
        if (!hodnota || hodnota === this._vek) return;
        this._vek = hodnota;
        // spuštění události pro změnu vlastnosti vek
        this.zmenaVlastnosti.fire(this, new ZmenaVlastnostiArgs("vek", hodnota));
    }
}

class KontrolaRegistrace {
    constructor(clovek) {
        this.clovek = clovek;
        // přihlášení k odběru události při změně vlastnosti objektu clovek
        // - protože metodu předáváme k pozdějšímu zavolání, tak metodou bind
        // - určujeme, čemu se bude rovnat klíčové slovo this až se bude metoda volat
        this.token = clovek.zmenaVlastnosti.subscribe(this.vekZmenen.bind(this));
    }

    // tato metoda se zavolá při změně věku člověka
    vekZmenen(odesilatel, args) {
        // pokud událost spustil objekt this.clovek a změnila
        // se vlastnost vek, tak proběhne tento kód
        if (odesilatel === this.clovek && args.nazev === "vek") {
            if (args.novaHodnota < 13) {
                console.log("Ještě se nemůžeš zaregistrovat, jsi moc mladý.");
            } else {
                console.log("Dosáhl jsi požadovaného věku k registraci. Můžeš se zaregistrovat.");
                // odhlášení odběru události při změně vlastnosti
                this.clovek.zmenaVlastnosti.unsubscribe(this.token);
            }
        }
    }
}


const clovek = new Clovek(11);
const kontrolaRegistrace = new KontrolaRegistrace(clovek);

// změnění věku člověka
clovek.vek++;
clovek.vek++;