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.
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.
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
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;
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();
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);