Návrhový vzor visitor umožňuje pro danou skupinu tříd dynamicky definovat nové operace, aniž by bylo nutné tyto třídy jakkoliv modifikovat. Visitor řadíme mezi vzory chování (behavioral) a patří mezi Gang of Four (GoF) vzory (je obsažen v bibli návrhových vzorů Design Patterns: Elements of Reusable Object-Oriented Software).
Implementace
Vzor visitor obsahuje dva typy tříd – třídy, které jsou navštěvované a třídy navštěvující (visitory). Navštěvované třídy v sobě nesou základní funkcionalitu, navštivující naopak přinášejí dynamicky (tj. za běhu aplikace) všem navštěvovaným třídám dodatečné chování.
Každá navštěvovaná třída musí být na příchod návštěvníka připravena – musí mít metodu (obvykle pojmenovanou accept) – která návštěvníka přijme a předá mu data, která potřebuje ke své práci. Jedná se o ukazatel na současnou instanci (this) a datové proměnné, ke kterým nemá návštěvník přístup (jsou například soukromé). Odkaz na aktuální instanci neslouží pouze k přenosu dat, rozhoduje také o tom, která metoda návštěvníka bude zavolána.
Příklad
Představme si, že máme třídy obecního úřadu a krajské policejní správy (obě třídy dědí z třídy AbstractAuthority), dále máme návštěvníka, který přidává oběma úřadům schopnost vydávat výpis z rejstříku trestů. V okamžiku, kdy si občan zažádá o výpis z rejstříku obecní úřad, tak třída úřadu přijme odpovídajícího návštěvníka a předá mu potřebnou infrastrukturu. Metoda návštěvníka pro obecní úřad zjistí, která úřednice má nejméně práce a přidá jí vyřízení žádosti do úkolů na další den. Pokud by občan přišel na krajskou policejní, tak bude volána metoda specifická pro třídu (systém) policejní správy, která využije místní databáze a vrátí navštěvované třídě požadovaný dokument (výpis).
Double dispatch
Ač se to není úplně patrné, tak nelze tento vzor implementovat jednodušeji – předat přetížené metodě visitor.visit abstraktního předka obou úřadů (AbstractAuthority) s tím, že se za běhu rozhodne dle skutečného typu parametru, zda-li bude volána metoda pro obecní úřad nebo policejní správu. Značná část programovacích jazyků totiž rozhoduje volání přetížených metod během kompilace. Pokud by tedy byl parametrem volání abstraktní předek, tak by se program ani nepřeložil (nešlo by rozhodnout, která metoda se má volat). Pokud by existovala varianta metody pro tohoto předka, tak by se program sice přeložil, ale byla by vždy volána jen pouze tato implementace (bez ohledu na to, že skutečným parametrem je jeden z potomků).
Tento nedostatek řeší výše uvedené double-dispatch schéma, ve kterém je nejprve volána metoda accept, která návštěvníkovi předá odkaz na aktuální instanci, pomocí něhož může bez problémů překladač rozhodnout, kterou variantu metody visit zavolat.
Rozšiřitelnost
Do programu, který využívá vzoru visitor je velmi snadné přidat novou funkcionalitu, jelikož stačí vytvořit nového návštěvníka. Kdybychom chtěli přidat úřadům schopnost vydávat pasy, tak bychom jen vytvořili třídu PassportVisitor, která by měla metodu visit přetíženou pro oba typy úřadu a implementovala pro ně specifickou funkcionalitu.
Z opačné strany je velmi obtížné vytvořit novou rozšiřovanou třídu, jelikož to znamená úpravu všech stávajících návštěvníků – přidání odpovídající přetížené varianty metody visit.
Diagram
Kód
001.
/**
002.
* Demonstrace navrhoveho vzoru Visitor
003.
* @author malejpavouk
004.
*/
005.
public
class
VisitorDemonstration {
006.
public
static
void
main(String[] args) {
007.
AbstractElement e =
new
BarElement();
//navstevovany objekt 1
008.
AbstractElement e2 =
new
ZooElement();
//navstevovany objekt 2
009.
VisitorInterface v =
new
FooVisitor();
//visitor pridavajici operaci foo
010.
011.
e.accept(v);
012.
System.out.println(
""
);
//odradkovani vystupu
013.
e2.accept(v);
014.
}
015.
}
016.
/**
017.
* Abstraktni element
018.
* @author malejpavouk
019.
*/
020.
abstract
class
AbstractElement {
021.
/**
022.
* Prijmi daneho visitora (metoda s promennym poctem parametru, do metody
023.
* budou parametry predany jako pole params)
024.
* @param v visitor
025.
* @param params parametry volani visitora
026.
*/
027.
public
abstract
Object[] accept(VisitorInterface v, Object... params);
028.
}
029.
/**
030.
* Navstevovana trida 1
031.
* @author malejpavouk
032.
*/
033.
class
BarElement
extends
AbstractElement {
034.
public
Object[] accept(VisitorInterface v, Object... params) {
035.
//zde muze objekt pridat do pole parametru nejake sve dodatecne vlastnosti
036.
System.out.println(
"BarElement: Adding some of my private field values to the params array"
);
037.
Object[] result = v.visit(
this
, params);
038.
//zde na zaklade vystupu metody muze element modifikovat svuj vnitrni stav
039.
//(soukromou cast - k te nemela metoda visitora pristup)
040.
System.out.println(
"BarElement: Modifying my inner state"
);
041.
042.
//skutecnym vystupem procesu muze byt pouze prvni prvek pole, ktere
043.
//generoval visitor
044.
Object[] newResult = {result[
0
]};
045.
046.
System.out.println(
"BarElement: Returning the call result: \\""
+ result[
0
] +
"\\""
);
047.
return
newResult;
048.
}
049.
/**
050.
* Udela operaci bar
051.
*/
052.
public
void
bar() {
053.
System.out.println(
"bar!!!"
);
054.
}
055.
}
056.
/**
057.
* Navstevovana trida
058.
* @author malejpavouk
059.
*/
060.
class
ZooElement
extends
AbstractElement {
061.
public
Object[] accept(VisitorInterface v, Object... params) {
062.
//zde muze objekt pridat do pole parametru nejake sve dodatecne vlastnosti
063.
System.out.println(
"ZooElement: Adding some of my private field values to the params array"
);
064.
Object[] result = v.visit(
this
, params);
065.
//zde na zaklade vystupu metody muze element modifikovat svuj vnitrni stav
066.
//(soukromou cast - k te nemela metoda visitora pristup)
067.
System.out.println(
"ZooElement: Modifying my inner state"
);
068.
069.
//skutecnym vystupem procesu muze byt pouze prvni prvek pole, ktere
070.
//generoval visitor
071.
Object[] newResult = {result[
0
]};
072.
073.
System.out.println(
"BarElement: Returning the call result: \\""
+ result[
0
] +
"\\""
);
074.
return
newResult;
075.
}
076.
/**
077.
* Udela operaci zoo
078.
*/
079.
public
void
zoo() {
080.
System.out.println(
"zoo!!!"
);
081.
}
082.
}
083.
/**
084.
* Interface navstevnika
085.
* @author malejpavouk
086.
*/
087.
interface
VisitorInterface {
088.
/**
089.
* Operace pridavana k bar elementu
090.
* @param e barelement, ke kteremu bude operace pridana
091.
* @param params parametry volani
092.
* @return vystup dane operace
093.
*/
094.
public
Object[] visit(BarElement e, Object... params);
095.
/**
096.
* Operace pridavana k zoo elementu
097.
* @param e zooelement, ke kteremu bude operace pridana
098.
* @param params parametry volani
099.
* @return vystup dane operace
100.
*/
101.
public
Object[] visit(ZooElement e, Object... params);
102.
}
103.
/**
104.
* Navstevnik, ktery udela operaci foo na danem elementu
105.
* @author malejpavouk
106.
*/
107.
class
FooVisitor
implements
VisitorInterface {
108.
/**
109.
* Tato operace bude pridana k bar elementu. Metoda ma variabilni pocet argumentu.
110.
* Tyto argumenty slouzi k predani vsech parametru, ktere metoda potrebuje (muze
111.
* se tak napriklad jednat i o soukrome vlastnosti bar elementu, ke kterym by jinak
112.
* nemela metoda pristup (pokud by nevyuzivala reflexi))
113.
* @param e barelement, ke kteremu bude metoda pridana
114.
* @param params parametry volani
115.
* @return navratova hodnota daneho volani
116.
*/
117.
public
Object[] visit(BarElement e, Object... params) {
118.
System.out.println(
"FooVisitor: Visiting "
+ e.getClass().toString());
119.
System.out.println(
"FooVisitor: foo method is working on the visited BarElement"
);
120.
System.out.println(
"FooVisitor: Modifying public BarElement state"
);
121.
122.
System.out.println(
"FooVisitor: Producing method result"
);
123.
Object[] result = {
"Foo visitor method return state"
,
"Params for visited element"
,
new
Object()};
124.
return
result;
125.
}
126.
/**
127.
* Tato operace bude pridana k zoo elementu. Metoda ma variabilni pocet argumentu.
128.
* Tyto argumenty slouzi k predani vsech parametru, ktere metoda potrebuje (muze
129.
* se tak napriklad jednat i o soukrome vlastnosti zoo elementu, ke kterym by jinak
130.
* nemela metoda pristup (pokud by nevyuzivala reflexi))
131.
* @param e barelement, ke kteremu bude metoda pridana
132.
* @param params parametry volani
133.
* @return navratova hodnota daneho volani
134.
*/
135.
public
Object[] visit(ZooElement e, Object... params) {
136.
System.out.println(
"FooVisitor: Visiting "
+ e.getClass().toString());
137.
System.out.println(
"FooVisitor: Doing something zooable"
);
138.
System.out.println(
"FooVisitor: foo method is working on the visited ZooElement"
);
139.
System.out.println(
"FooVisitor: Modifying public ZooElement state"
);
140.
141.
System.out.println(
"FooVisitor: Producing method result"
);
142.
Object[] result = {
"Foo visitor method return state"
,
"Params for visited element"
,
new
Integer(
5
)};
143.
return
result;
144.
}
145.
}
01.
BarElement: Adding some of my private field values to the params array
02.
FooVisitor: Visiting class algoritmy.designpatterns.Visitor.BarElement
03.
FooVisitor: foo method is working on the visited BarElement
04.
FooVisitor: Modifying public BarElement state
05.
FooVisitor: Producing method result
06.
BarElement: Modifying my inner state
07.
BarElement: Returning the call result: "Foo visitor method return state"
08.
09.
ZooElement: Adding some of my private field values to the params array
10.
FooVisitor: Visiting class algoritmy.designpatterns.Visitor.ZooElement
11.
FooVisitor: Doing something zooable
12.
FooVisitor: foo method is working on the visited ZooElement
13.
FooVisitor: Modifying public ZooElement state
14.
FooVisitor: Producing method result
15.
ZooElement: Modifying my inner state
16.
ZooElement: Returning the call result: "Foo visitor method return state"