Visitor
Situace
Je definována hiearchie tříd se společným předkem. Tento předek
deklaruje metodu, která je v každé podtřídě implementována jinak.
Zdrojový kód všech konkrétních implementací se má z hiearchie extrahovat
a přesunout jinam, například do jedné externí třídy.
Problém
V programovacích jazycích, které podporují tzv. multiple
dispatch, stačí využít přetěžování metod. Metoda se
přetíží pro každou podtřídu a výběr té správné metody zajistí již
jazyk automaticky. V ostatních jazycích (které podporují pouze
single dispatch) se tento mechanizmus musí simulovat.
Externí třída, do které byly implementace přesunuty, veřejně vystaví
pouze tu nejobecnější možnou metodu – tedy tu, která jako parametr
přijímá kořenovou třídu hiearchie. Tato metoda pak musí podle
konkrétního typu parametru zvolit správnou metodu a spustit ji.
Triválně se tento problém řeší kaskádou podmínek if, které
za běhu ověřují typ parametru a podle něho volají konkrétní metodu.
kód v jazyce Java - Zobrazit
-
public void
doSomething(Parent a)
-
{
-
if (a instanceof Child1)
-
{
-
// zavolat implementaci pro
třídu Child1
-
this.doSomething((Child1) a);
-
}
-
else if (a instanceof Child2)
-
{
-
// zavolat implementaci pro
třídu Child2
-
this.doSomething((Child3) a);
-
}
-
else if (a instanceof Child3)
-
{
-
// zavolat implementaci pro
třídu Child3
-
this.doSomething((Child3) a);
-
}
-
else
-
{
-
throw new IllegalArgumentException();
-
}
-
}
-
-
protected void doSomething(Child1 a) {...}
-
protected void doSomething(Child2 a) {...}
-
protected void doSomething(Child3 a) {...}
Tento způsob ale není příliš dobrý, protože je značně nepřehledný
a podmínky se musí udržovat konzistentní s aktuální hiearchií
odpovídajících tříd. Navíc se tak zbytečně řeší problém, který by
teoreticky vzniknout ani nemusel.
Řešení
Obtížně udržovatelná a těžce srozumitelná kaskáda podmínek
if se nahradí elegantní spoluprací dvou tříd, které jsou zde
označeny jako Place a Visitor. Třída
Place požádá vybranou instanci třídy
Visitor o provedení akce a předá ji sama sebe jako
parametr. Třída Visitor pak na této instanci zavolá
požadovanou metodu, která odpovídá její třídě.
Takto se simuluje double dispatch v jazycích, které ho
nemají. V principu se k identitě příjemce metody přidává ještě druhá
a chybějící informace o odesílateli.
UML diagramy
Příklad
Následuje jednoduchý příklad implementace tohoto vzoru v programovacím jazyce Java.
Cíle návštěv
Cíle návštěv budou dva. První z nich bude kino, druhé muzeum. Oba
cíle budou implementovat společné rozhraní.
kód v jazyce Java - Zobrazit
-
/**
-
* Nějaké zajímavé
místo.
-
*
-
* @author Vojtěch
Hordějčuk
-
*/
-
public interface Place
-
{
-
/**
-
* Přišel
návštěvník.
-
*
-
* @param
visitor
-
*
návštěvník
-
*/
-
public void accept(Visitor visitor);
-
}
-
-
/**
-
* Kino.
-
*
-
* @author Vojtěch
Hordějčuk
-
*/
-
public class
Cinema implements Place
-
{
-
@Override
-
public void accept(Visitor visitor)
-
{
-
System.out.println("do kina přišel " +
visitor.toString());
-
visitor.visit(this);
-
}
-
}
-
-
/**
-
* Muzeum.
-
*
-
* @author Vojtěch
Hordějčuk
-
*/
-
public class
Museum implements Place
-
{
-
@Override
-
public void accept(Visitor visitor)
-
{
-
System.out.println("do muzea přišel "
+ visitor.toString());
-
visitor.visit(this);
-
}
-
}
Návštěvníci
Návštěvníci budou dva. První z nich bude představovat hodného
návštěvníka, druhý zlého. Oba budou implementovat společné
rozhraní.
kód v jazyce Java - Zobrazit
-
/**
-
* Obecný
návštěvník.
-
*
-
* @author Vojtěch
Hordějčuk
-
*/
-
public interface Visitor
-
{
-
/**
-
* Navštíví zadaný
koncert.
-
*
-
* @param
concert
-
* cílový
koncert
-
*/
-
public void visit(Museum concert);
-
-
/**
-
* Navštíví zadané
kino.
-
*
-
* @param cinema
-
* cílové kino
-
*/
-
public void visit(Cinema cinema);
-
}
-
-
/**
-
* Hodný
návštěvník.
-
*
-
* @author Vojtěch
Hordějčuk
-
*/
-
public class
GoodVisitor implements Visitor
-
{
-
@Override
-
public void visit(Museum museum)
-
{
-
System.out.println(this.toString() + " je v muzeu");
-
}
-
-
@Override
-
public void visit(Cinema cinema)
-
{
-
System.out.println(this.toString() + " je v kině");
-
}
-
-
@Override
-
public String toString()
-
{
-
return "pan Hodný";
-
}
-
}
-
-
/**
-
* Zlý návštěvník.
-
*
-
* @author Vojtěch
Hordějčuk
-
*/
-
public class
EvilVisitor implements Visitor
-
{
-
@Override
-
public void visit(Museum museum)
-
{
-
System.out.println(this.toString() + " vykradl
muzeum.");
-
}
-
-
@Override
-
public void visit(Cinema cinema)
-
{
-
System.out.println(this.toString() + " udělal nepořádek v
kině.");
-
}
-
-
@Override
-
public String toString()
-
{
-
return "pan Zlý";
-
}
-
}
Test
Vytvoří se oba druhy návštěvníků. Poté se vytvoří i obě cílová
místa. Nakonec se návštěvníci pošlou do obou cílů.
kód v jazyce Java - Zobrazit
-
public static
void main(String[] args)
-
{
-
// vytvořit
návštěvníky
-
-
Visitor good = new
GoodVisitor();
-
Visitor evil = new
EvilVisitor();
-
-
// vytvořit cíle
-
-
Place cinema = new Cinema();
-
Place museum = new Museum();
-
-
// hodný návštěvník
-
-
cinema.accept(good);
-
museum.accept(good);
-
-
// zlý návštěvník
-
-
cinema.accept(evil);
-
museum.accept(evil);
-
}
Reference