Proxy


V této části se podíváme na návrhový vzor Proxy. V nejobecnější podobě se jedná o třídu, která slouží jako rozhraní pro něco jiného.

Proč Proxy použít

Občas můžeme chtít u nějaké třídy přidat/pozměnit funkcionalitu. Pomocí proxy například můžeme různě kontrolovat přístup k objektu nebo třeba můžeme přidávat nějakou další funkcionalitu, když se zavolá metoda třídy.

K vytvoření proxy jednoduše replikujeme rozhraní existující třídy a u některých metod předěláváme/přidáváme funkcionalitu.

Příklad - Proxy pro hodnotu

Proxy si nemusíme vytvářet jen pro nějakou třídu, ale můžeme si ji vytvořit třeba i pro nějakou hodnotu. Následující ukázka ukazuje proxy pro hodnotu, která představuje procento. Když s procentem pracujeme, tak se mění jeho hodnota.

// třída pro procentuální hodnoty
class Procento {
    constructor(procento) {
        this.procento = procento;
    }

    // pomocí této metody určujeme, co se použije při
    // použití objektu třídy Procento s řetězci
    toString() {
        return `${this.procento}%`;
    }

    // PROXY pro hodnotu procenta - měníme jeho hodnotu, když jej použijeme
    // - pomocí této metody určujeme hodnotu, která se použije, když
    // - objekt třídy Procento použijeme třeba pro nějaký výpočet
    valueOf() {
        return this.procento/100;
    }
}

const petProcent = new Procento(5);
// pokud budeme s procentem pracovat, tak se nejdříve automaticky vydělí 100
console.log(`${petProcent} z 50 je ${50*petProcent}`);
// vypíše se: 5% z 50 je 2.5

Příklad - Proxy pro vlastnost objektu

Proxy si můžeme vytvořit také například pro vlastnosti objektu. V následující ukázce se vytváří objekt, který obsahuje vlastnost, kterou když změníme, tak se nám o tom vypíše informace do konzole.

// Proxy, kterou můžeme použít jako vlastnost objektu
class Vlastnost {
    constructor(hodnota, jmeno) {
        this._hodnota = hodnota;
        this.jmeno = jmeno;
    }

    get hodnota() { return this._hodnota; }
    set hodnota(novaHodnota) {
        if (this._hodnota === novaHodnota) return;
        this._hodnota = novaHodnota;
        // kromě toho, že se změní hodnota vlastnosti, tak se
        // nám o tom ještě navíc vypíše informace do konzole
        console.log(`Proběhla změna vlastnosti ${this.jmeno} na ${novaHodnota}.`);
    }
}

class Nestvura {
    constructor() {
        this._zivoty = new Vlastnost(10, "zivoty");
    }

    get zivoty() { return this._zivoty.hodnota; }
    set zivoty(hodnota) {
        this._zivoty.hodnota = hodnota;
    }
}


const nestvura = new Nestvura();
// když budeme měnit vlastnost zivoty objektu nestvura,
// tak se nám o tom bude vypisovat informace do konzole
nestvura.zivoty = 9;
nestvura.zivoty = 7;

Příklad - Ochranná Proxy

Proxy můžeme chtít použít, když chceme omezit přístup k některým metodám třídy podle nějaké podmínky. Následující ukázka ukazuje třídu Auto, která obsahuje metodu ridit. Také ukazuje třídu ChraneneAuto, která je proxy pro třídu Auto a zajišťuje, že se metoda ridit třídy Auto nezavolá, pokud má řidič méně jak 18 let.

class Auto {
    ridit() {
        console.log("Auto je řízeno.");
    }
}

// Proxy pro třídu Auto
class ChraneneAuto {
    constructor(ridic) {
        this.ridic = ridic;
        this._auto = new Auto();
    }

    // v metode ridit se kontroluje jestli má
    // řidič věk na to aby mohl řídit auto
    ridit() {
        if (this.ridic.vek >= 18) {
            this._auto.ridit();
        } else {
            console.log("Řidič nemůže řídit auto.");
        }
    }
}

class Ridic {
    constructor(vek) {
        this.vek = vek;
    }
}


const ridic = new Ridic(17);
// vytvoření instance třídy ChraneneAuto
// - můžeme ji používat stejným způsobem jako třídu Auto, má stejné metody
const auto = new ChraneneAuto(ridic);

// řidič má 17 let, ještě řídit nemůže
auto.ridit();
ridic.vek++;
// řidič má 18 let, již auto řídit může
auto.ridit();

Příklad - Virtuální Proxy

Virtuální proxy je proxy, která zajišťuje, že se objekty, které jsou náročné na vytvoření plně vytvoří až při první interakci s objektem. Následující ukázka ukazuje třídu Obrazek a třídu LazyObrazek. Třída Obrazek reprezentuje obrázek, který se načte při vytvoření instance třídy a můžeme jej vykreslit metodou vykresli. Třída LazyObrazek je Proxy pro třídu Obrazek a zajišťuje, že se obrázek načte až při prvním zavolání metody vykresli.

// třída představující obrázek
// - obrázek se načte při vytvoření objektu
// - načte se i když nikdy nezavoláme metodu vykresli
class Obrazek {
    constructor(url) {
        this.url = url;
        // simulace načtení obrázku
        console.log(`Obrázek se načítá z ${url}`);
    }

    // metoda pro simulaci vykreslení obrázku
    vykresli() {
        console.log(`Vykresluji obrázek (${this.url}).`);
    }
}

// Proxy pro třídu Obrazek
// - implementace lazy loadingu
// - obrázek se načte až při volání metody vykresli
class LazyObrazek {
    constructor(url) {
        this.url = url;

    }

    vykresli() {
        // obrázek se načte (vytvoří) až se poprvé zavolá metoda vykresli
        if (!this.obrazek) this.obrazek = new Obrazek(this.url);
        this.obrazek.vykresli();
    }
}


// funkce pro vykreslení obrázku (abychom viděli, kdy se obrázek načítá)
function vykresliObrazek(obrazek) {
    console.log("Bude se vykreslovat obrázek.");
    obrazek.vykresli();
    console.log("Obrázek se vykreslil.");
}

const obrazek1 = new Obrazek("http://localhost/obrazky/obrazek1.jpg");
const obrazek2 = new LazyObrazek("http://localhost/obrazky/obrazek2.jpg");
vykresliObrazek(obrazek1);
console.log("-------------------");
vykresliObrazek(obrazek2);