V této části se podíváme na návrhový vzor Visitor. Díky Visitoru můžeme přidat kolekci objektů novou operaci bez její modifikace.
Občas můžeme chtít provádět nějakou operaci například s celou objektovou strukturou, ale nechceme měnit třídy z kterých se skládá. Můžeme použít návrhový vzor Visitor, který slouží pro nevtíravé přidání nové funkcionality. Visitor je komponenta, která umí procházet datovou strukturu, skládající se z určitých (možná souvisejících) typů a pracuje s ní. Visitor v češtině znamená návštěvník, jedná se tedy o komponentu, která něco navštěvuje.
Jako první příklad si ukážeme méně vhodný způsob implementace Visitora. Neznamená to ale, že bychom něco takového nikdy neměli dělat, záleží na nás.
Následující třída ukazuje třídy CiselnyVyraz a ScitaciVyraz, s jejichž pomocí můžeme sestavit nějaký matematický výraz (strukturu). Tyto třídy obsahují metodu print, která slouží k získání textové reprezentace výrazu. Metoda print bere jako parametr pole, které je Visitorem (navštěvuje objekt) a naplňuje jej různými řetězci.
class CiselnyVyraz {
constructor(hodnota) {
this.hodnota = hodnota;
}
// tato metoda bere jako parametr pole buffer
// - pole buffer je Visitor
print(buffer) {
buffer.push(this.hodnota.toString());
}
}
class ScitaciVyraz {
constructor(levaStrana, pravaStrana) {
this.levaStrana = levaStrana;
this.pravaStrana = pravaStrana;
}
// tato metoda bere jako parametr pole buffer
// - pole buffer je Visitor
print(buffer) {
buffer.push('(');
this.levaStrana.print(buffer);
buffer.push('+');
this.pravaStrana.print(buffer);
buffer.push(')');
}
}
// reprezentace matematického výrazu: 1+(2+3)
const v = new ScitaciVyraz(
new CiselnyVyraz(1),
new ScitaciVyraz(
new CiselnyVyraz(2),
new CiselnyVyraz(3)
)
);
const buffer = [];
// v tomto případě je pole buffer Visitor (navštěvuje
// třídy ScitaciVyraz a CiselnyVyraz)
v.print(buffer);
console.log(buffer.join('')); // (1+(2+3))
Problém této implementace je v tom, že jsme si pro Visitora museli ve třídách CiselnyVyraz a ScitaciVyraz vytvořit metodu print. Pokud nechceme tyto třídy vždy při přidávání nové operace modifikovat, tak to tímto způsobem udělat nemůžeme.
V dalším příkladu si pro Visitora vytvoříme speciální komponentu. Ta bude obsahovat metodu print, kterou Visitor může použít. Takže ve třídách CiselnyVyraz a ScitaciVyraz metodu print nepotřebujeme a nemusíme je tedy modifikovat. To už je o něco lepší implementace než v předchozím příkladu, ale můžeme to udělat ještě lépe. Nejlepší způsob implementace Visitora je ukázán v posledním příkladu.
Následující ukázka ukazuje třídu PrinterVyrazu, obsahující metodu print, která bere jako parametr matematický výraz a pole, do kterého se mají uložit řetězce. Třída PrinterVyrazu pracuje s objekty třídy CiselnyVyraz a ScitaciVyraz tak jak jsou a nemusíme je tedy pro Visitora modifikovat.
class CiselnyVyraz {
constructor(hodnota) {
this.hodnota = hodnota;
}
}
class ScitaciVyraz {
constructor(levaStrana, pravaStrana) {
this.levaStrana = levaStrana;
this.pravaStrana = pravaStrana;
}
}
// komponenta umožňující Visitorovi provádět
// operace se třídami CiselnyVyraz a ScitaciVyraz
class PrinterVyrazu {
// tato metoda bere jako parametr výraz a pole buffer
// - pole buffer je Visitor
print(vyraz, buffer) {
if (vyraz instanceof CiselnyVyraz) {
buffer.push(vyraz.hodnota);
} else if (vyraz instanceof ScitaciVyraz) {
buffer.push('(');
this.print(vyraz.levaStrana, buffer);
buffer.push('+');
this.print(vyraz.pravaStrana, buffer);
buffer.push(')');
}
}
}
// reprezentace matematického výrazu: 1+(2+3)
const v = new ScitaciVyraz(
new CiselnyVyraz(1),
new ScitaciVyraz(
new CiselnyVyraz(2),
new CiselnyVyraz(3)
)
);
// toto pole je Visitor
const buffer = [];
// tuto komponentu může Visitor (pole buffer) použít
const printer = new PrinterVyrazu();
printer.print(v, buffer);
console.log(buffer.join('')); // (1+(2+3))
Na závěr si ukážeme pravděpodobně nejlepší způsob implementace Visitoru. Je pro něj potřeba modifikovat třídy CiselnyVyraz a ScitaciVyraz, ale jedná se jen o přidání jedné metody, kterou může Visitor zavolat, a být tak instancemi těchto tříd přijat.
Následující ukázka třídám CiselnyVyraz a ScitaciVyraz přidává metodu prijmi. Tuto metoda může být zavolána Visitorem, který jí jako argument předá sám sebe. V metodě prijmi se zavolá metoda Visitora s argumentem this a Visitor může v této metodě s předaným objektem provést nějakou akci. Abych ukázal, že třídy CiselnyVyraz a ScitaciVyraz potřebují jen metodu prijmi, tak se v ukázce kromě Visitoru PrinterVyrazu nachází ještě Visitor KalkulatorVyrazu.
class CiselnyVyraz {
constructor(hodnota) {
this.hodnota = hodnota;
}
// tato metoda je volána Visitorem
// - Visitor se předává jako parametr
prijmi(visitor) {
// zavolá se metoda navstivCislo předaného Visitoru s parametrem this
visitor.navstivCislo(this);
}
}
class ScitaciVyraz {
constructor(levaStrana, pravaStrana) {
this.levaStrana = levaStrana;
this.pravaStrana = pravaStrana;
}
// tato metoda je volána Visitorem
// - Visitor se předává jako parametr
prijmi(visitor) {
// zavolá se metoda navstivScitani předaného Visitoru s parametrem this
visitor.navstivScitani(this);
}
}
// Visitor
// - slouží k vypsání matematického výrazu
class PrinterVyrazu {
constructor() {
this.buffer = [];
}
navstivCislo(vyraz) {
this.buffer.push(vyraz.hodnota);
}
navstivScitani(vyraz) {
this.buffer.push('(');
// navštívení levé části výrazu
vyraz.levaStrana.prijmi(this);
this.buffer.push('+');
// navštívení pravé části výrazu
vyraz.pravaStrana.prijmi(this);
this.buffer.push(')');
}
toString() {
return this.buffer.join('');
}
}
// další Visitor
// - slouží k vypočítání matematického výrazu
class KalkulatorVyrazu {
constructor() {
this.vysledek = 0;
}
navstivCislo(vyraz) {
this.vysledek = vyraz.hodnota;
}
navstivScitani(vyraz) {
// navštívení levé části výrazu
vyraz.levaStrana.prijmi(this);
let temp = this.vysledek;
// navštívení pravé části výrazu
vyraz.pravaStrana.prijmi(this);
this.vysledek += temp;
}
}
// reprezentace matematického výrazu: 1+(2+3)
const v = new ScitaciVyraz(
new CiselnyVyraz(1),
new ScitaciVyraz(
new CiselnyVyraz(2),
new CiselnyVyraz(3)
)
);
// použití Visitoru k získání textové reprezentace výrazu
const printer = new PrinterVyrazu();
printer.navstivScitani(v);
// použití Visitoru k získání výsledku výrazu
const kalkulator = new KalkulatorVyrazu();
kalkulator.navstivScitani(v);
console.log(`${printer.toString()} = ${kalkulator.vysledek}`); // (1+(2+3)) = 6