Naar aanleiding van 'ojee, wat is het nivo op got toch weer laag' gebeuren zouden we artikelen en tutorials schrijven om op GoT te plaatsen. Dit is een preview van een artikel wat ik in elkaar heb geplakt en wil graag jullie mening erover weten. Het is nog niet helemaal klaar en de typo`s/kromme zinnen moeten er ook nog uitgehaald worden
Helaas is op dit moment de code in Java, dus voor mensen die de taal niet beheersen kan het wat lastiger lezen zijn.
VISITOR DESIGN PATTERN.
VOORWOORD
Het visitor design pattern is een pattern waarmee op een uitzonderlijke fraaie manier functionaliteit toegevoegd kan worden aan classes, blabla
Door Peter Veentjer alias Alarmnummer.
INLEIDING
Ik wil graag met jullie het visitor design pattern delen, omdat dit namelijk een zeer krachtig design pattern is waarmee je op een hele fraaie manier functionaliteit toe kan voegen aan objecten ipv dit bij objecten zelf te implementeren. Er kleven 3 nadelen aan de laatste aanpak:
1)als je de sourcecode niet hebt dan is het lastig om nieuwe functionaliteit toe te voegen.
2)doordat je uiteindelijk veel methodes krijgt in je classes, worden ze vaak zeer onoverzichtelijk.
3)de logica van een stuk functionaliteit ligt verspreid over een hele hierarchie van objecten en daardoor krijg je minder snel inzicht.
Met het Visitor design pattern kan dit grotendeels verholpen worden, dus het is een uitermate krachtig en handige design pattern en naar mijn mening is dit veruit de mooiste uit het Design Patterns - Elements of Reusable Object Oriented Software boek (ook wel bekend als het GoF boek)
Maar om het visitor design pattern te begrijpen zal ik eerst uitleggen wat de term 'dispatch' inhoud.
UITLEG DISPATCH
Stel dat ik de volgende interface en implementaties voor een elementair typesysteem heb:
En ik heb de volgende aanroepende code:
Type t = new StringType();
t.print();
Dan krijg je dus 'string' op je scherm omdat runtime uitgezocht wordt welke implementatie van print() bij t hoort, dit heet ook wel polymorphisme.
Om dispatch wat beter uit te leggen ga ik jullie eerst uitleggen hoe je een niet statische methode als statische functie kan schrijven. Als je een niet statische methode hebt, dan heeft de methode beschikking over het object waarop de methode wordt uitgevoerd. Maar hoe kan je dit als statische functie schrijven, want een statische functie heeft dus geen object waarop hij wordt uitgevoerd. Als je het object waarop die functie uitgevoerd moet worden nou eens meegeeft als argument, dan heeft die functie dus wel beschikking over dat object. Dit gebeurt onder de grond ook bij java, daar wordt namelijk dit argument impliciet meegegeven en die kan je gewoonlijk benaderen via de 'this' variable. Gelukkig hoef je niet altijd expliciet this op te geven, maar bij compilatie wordt dit er wel gewoon aan toegevoegd. Het komt er dus op neer dat er in principe geen verschil bestaat tussen een methode en een functie.
Hieronder staat een voorbeeld van de conversie van print methodes naar print functies. Ze zuhb meteen in een aparte class gezet zodat je een goed overzicht krijgt.
Zoals je ziet heeft iedere functie nu beschikking over het object waar hij eventueel allerlei data uit kan lezen of acties op uit kan voeren.
Als je nu het volgende aanroepende code zou schrijven:
Type type = new StringType();
Print.print(type)
Dan krijg je dus weer 'string' op het scherm. De reden dat er 'string' komt te staan, is dat er runtime uitgezocht wordt welke functie uitgevoerd moet worden. Dit heet ook wel dispatch: de runtime selectie van de juiste functie/methode op basis van de argument types.
In dit geval gebeurt dit op basis van 1 argument, namelijk t, dit heet dan ook wel een single dispatch. Als je 2 argumenten gebruikt om de juiste keuze te maken, dan heb je een double dispatch, en bij n argumenten is het een multidispatch. En een methode die gebruikt maakt van multidispatch heet ook wel een multimethod.
Als bij een multi/double dispatch de lexografische volgorde van argumenten belangrijk is dan spreek je van een asymetrische multidispatch en anders van een symetrische, dit gegeven is verder niet interessant voor het visitor designpattern maar volledigheidswijze vermeld ik het wel even.
Verder zou je je kunnen afvragen of de gebruikte aanroepende syntax voor deze 'externe' functies nou zo handig is. En daarnaast zal zo`n functie beschikking moeten hebben over data die je eventueel niet zou willen laten zien. Om dit probleem te verhelpen zijn 'open objects' uitgevonden. Dit zijn objecten waarop op een soortgelijke manier extern functies toegevoegd kunnen worden terwijl ze wel beschikking hebben over allerlei private informatie.
In onderstaande code geef ik een voorbeeld van symetrische double dispatched functies om te bepalen of een type een subtype is van een ander type.
Als je deze functie gaat aanroepen met isSubType(new IntType(),new RealType()), dan weet je dat de 2e functie wordt gekozen en dat je een true als antwoord krijgt. Als je aanroept met: isSubType(new StringType(),new RealType()) dan voldoet alleen de laatste functie en krijg je dus een false terug.
Ik vind persoonlijke de bovenstaande code erg mooi en dit zie je wel vaker in functionele programmeertalen onder de algemene noemer: pattern matching. (Pattern matching kan bij functionele talen vaak nog veel meer dan alleen multidispatch). En verder is het bij functionele programmeertalen gebruikelijk om een functie te maken over meerdere types en dat zie je hier ook in terug.
Er is alleen een groot probleem. Bovenstaande single en multidispatch voorbeelden zullen in talen zoals Java, c++, c#, Delphi niet werken omdat zij alleen single dispatch mbv polymorphisme bezitten en geen algemene single dispatch en al helemaal geen double of multidispatch. Daarnaast zal isSubType code ook niet gaan werken omdat java gebruik maakt van de meest ruime methode selectie en niet de meest strikte methode selectie. Aangezien de laatste methode de ruimste is (alle types kunnen daar terecht) zal deze methode altijd uitgevoerd worden en die anderen dus niet.
UITLEG VISITORS.
Wat een Visitor doet is gebruik maken van het mechanisme van polymorphistische dispatch om algemene single dispatch te verkrijgen.
Dit gebeurt mbv een callback op de volgende manier:
Je maakt eerst een algemene visitor interface waarin alle Types staan. Je maakt het mogelijk dat een visitor ieder type kan bezoeken mbv een accepts(Visitor v) methode. En daarbij ga je het polymorphe gedrag van die methode gebruiken om die dispatch over te brengen op de visitor interface (de callback). Als je bv een IntType hebt en je gaat met een visitor op bezoek, dan zal dus de visit(IntType t) worden uitgevoerd uit de Visitor interface. Dus op basis van het type wordt de juiste methode aangeroepen op de interface. En nu hebben we dus onze algemene single dispatch.
DE DEFAULT VISIT EN ADAPTERS.
Soms is het vrij vervelend om alle methodes van een interface te moeten implementeren. Je zou dan een Adapter kunnen maken voor die interface waarin iedere methode al een lege implementatie heeft.
Als je nu alleen bij een string een actie wilt ondernemen, kan je volstaan met deze code.
Zoals je ziet hoef je nu alleen nog maar de interessante methodes te overriden, en bij de rest wordt een lege methode aangeroepen. Het missende aan deze aanpak is dat je nu geen actie kan ondernemen op de rest. Dit zal ik verduidelijken aan de hand van het volgende voorbeeld. Stel dat je wilt bepalen of iets wel of geen getal is. Je zou dan bij de visit(IntType t) en visit(RealType t) de waarde op true kunnen zetten, en bij visit(String t) zet je hem op false. Om dit voor elkaar te krijgen zou je een default visit kunnen maken, die iedere visit methode aanroept als het niet overriden wordt.
DOUBLE AND MULTIDISPATCH MBV VISITOR.
Zoals vaak ten onrechte wordt gezegd krijg je met een visitor dus geen double dispatch, maar een single dispatch. Alleen wordt dit dus gedaan aan de hand van double dispatch. Je zoekt namelijk eerst de juiste visit implementatie uit mbv polymorphisme (1e dispatch) en later wordt in de interface de juiste methode gekozen. Dit gebeurt compile time, en heet daarom ook wel een statische dispatch. Hiermee hebben we ook meteen onze 2e dispatch te pakken.
Maar je kan wel een double/multi dispatched versie maken aan de hand van meerdere visitor calls.
Voorbeeld van een double dispatched versie (ik heb de StringType er even uitgelaten ivm lengte code).
En aanroepen met:
Type intType = new IntType();
Type realType = new RealType();
IsSubType isSubType = new IsSubType();
intType.accepts(new DoubleDispatcher(realType,isSubType));
boolean result = isSubType.getResult();
Eventueel kan dit natuurlijk nog wel wat opgeschoond worden.
En IsSubType zou je zelfs nog kunnen vereenvoudigen mbv een algemene adapter class naar:
Of zelfs tot:
Een voor multidispatched versie kan je nu ook wel een soorgelijke oplossing bedenken. Het probleem aan deze aanpak is alleen dat het dus enorm in de methodes kan komen te lopen, tenslotte moet je voor iedere n types en m dispatches n^m visit methodes schrijven.
VOORBEELD VISITOR IMPLEMENTATIE.
Nu we dus beschikking hebben over een single dispatch hoeven we alleen nog maar een implementatie te maken voor die Visitor interface om daar functionaleit aan te koppelen. Ik zou nu de volgende PrintVisitor kunnen maken:
Als je dit nu aanroept met:
Type p = new StringType();
p.accepts(new PrintVisitor());
Dan krijg je dus 'string' op je scherm te staan.
Zoals je ziet is het dus nu erg eenvoudig om nieuwe functionaliteit aan classes toe te voegen zonder dat die classes daarvoor veranderd moeten worden. Ze hoeven dus alleen een accepts methode te hebben en later kan daar nieuwe functionliteit aan worden toegevoegd.
Om het gemak van de visitor nog wat verder te verduidelijken staan hieronder nog een aantal visitor implementaties.
VOOR EN NADELEN VISITORS.
Het is verder gebruikelijk om bij design patterns de voor en nadelen ook te vermelden en dat wil ik jullie dan ook niet onthouden.
Voordelen aan visitors:
1) je objecten blijven schoner omdat je allerlei handige functies niet in de class zelf hoeft te plaatsen.
2) je krijgt een hele mooie bundeling in 1 object van een bepaald stuk functionaliteit zodat het niet verspreid ligt over je class hierarchie.
3) in een visitor kan je allerlei toestands informatie meenemen die gewoonlijk als argument in de methode aanroep meegenomen had moet worden. Dit is vooral handig bij traversal over een structuur heen en ik zal in het volgende deel op dit punt terug komen.
Nadelen aan de visitors:
1) het kan lastig zijn om nieuwe classes aan je hierarchie toe te voegen. Stel dat je geen beschikking hebt over de visitor interface source dan kan je geen nieuwe visit methode toevoegen. In A Pattern Language To Visitors geven ze een oplossing voor dit probleem: de element adder. Als je wel beschikking hebt over de source van de Visitor interface maar andere programmeurs hebben een implementatie hiervan gemaakt. Dan kan krijg je dus in de implementatie een methode te weinig en zal je een foutmelding krijgen dat een methode niet gevonden is. Dit is verder wel een zeer vervelend probleem.
2) Als je de sources niet hebt van een class (hierarchie) dan zal is het vrijwel onmogelijk om het Visitor design pattern in te bouwen.
3) Zo nu en dan moet je ecapsulation van objecten verbreken omdat je dus nu allerlei logica buiten de bezochte objecten gaat plaatsen en je toch hele specifieke informatie nodig hebt of acties op worden ondernomen.
Ik hoop dat jullie wat opgestoken hebben van mijn verhaal want ik heb er zelf wel plezier in gehad, terwijl schrijven zeker niet eens van mijn sterke punten is. En als er genoeg interesse is zal ik binnenkort nog een hele krachtige uitbreiding op het Visitor design pattern plaatsen, namelijk 'Visitor Guides'.
Literatuur opgave:
-Design Patterns - Elements of Reusable Object Oriented Software
http://www.amazon.com/exec/obidos/tg/detail/-/0201633612
-MultiJava: Design, implementation, and evaluation of a Java-compatible language supporting modular open classes and symmetric multiple dispatch
http://www.cs.iastate.edu...tijava/papers/TR01-10.pdf
-A Pattern Language To Visitors
http://jerry.cs.uiuc.edu/...mai0/PLoP2001_ymai0_1.pdf
VISITOR DESIGN PATTERN.
VOORWOORD
Het visitor design pattern is een pattern waarmee op een uitzonderlijke fraaie manier functionaliteit toegevoegd kan worden aan classes, blabla
Door Peter Veentjer alias Alarmnummer.
INLEIDING
Ik wil graag met jullie het visitor design pattern delen, omdat dit namelijk een zeer krachtig design pattern is waarmee je op een hele fraaie manier functionaliteit toe kan voegen aan objecten ipv dit bij objecten zelf te implementeren. Er kleven 3 nadelen aan de laatste aanpak:
1)als je de sourcecode niet hebt dan is het lastig om nieuwe functionaliteit toe te voegen.
2)doordat je uiteindelijk veel methodes krijgt in je classes, worden ze vaak zeer onoverzichtelijk.
3)de logica van een stuk functionaliteit ligt verspreid over een hele hierarchie van objecten en daardoor krijg je minder snel inzicht.
Met het Visitor design pattern kan dit grotendeels verholpen worden, dus het is een uitermate krachtig en handige design pattern en naar mijn mening is dit veruit de mooiste uit het Design Patterns - Elements of Reusable Object Oriented Software boek (ook wel bekend als het GoF boek)
Maar om het visitor design pattern te begrijpen zal ik eerst uitleggen wat de term 'dispatch' inhoud.
UITLEG DISPATCH
Stel dat ik de volgende interface en implementaties voor een elementair typesysteem heb:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| interface Type{ void print(); } class IntType implements Type{ void print(){System.out.println("int");} } class RealType implements Type{ void print(){System.out.println("real");} } class StringType implements Type{ void print(){System.out.println("string");} } |
En ik heb de volgende aanroepende code:
Type t = new StringType();
t.print();
Dan krijg je dus 'string' op je scherm omdat runtime uitgezocht wordt welke implementatie van print() bij t hoort, dit heet ook wel polymorphisme.
Om dispatch wat beter uit te leggen ga ik jullie eerst uitleggen hoe je een niet statische methode als statische functie kan schrijven. Als je een niet statische methode hebt, dan heeft de methode beschikking over het object waarop de methode wordt uitgevoerd. Maar hoe kan je dit als statische functie schrijven, want een statische functie heeft dus geen object waarop hij wordt uitgevoerd. Als je het object waarop die functie uitgevoerd moet worden nou eens meegeeft als argument, dan heeft die functie dus wel beschikking over dat object. Dit gebeurt onder de grond ook bij java, daar wordt namelijk dit argument impliciet meegegeven en die kan je gewoonlijk benaderen via de 'this' variable. Gelukkig hoef je niet altijd expliciet this op te geven, maar bij compilatie wordt dit er wel gewoon aan toegevoegd. Het komt er dus op neer dat er in principe geen verschil bestaat tussen een methode en een functie.
Hieronder staat een voorbeeld van de conversie van print methodes naar print functies. Ze zuhb meteen in een aparte class gezet zodat je een goed overzicht krijgt.
code:
1
2
3
4
5
| class Print{ static void print(IntType t){System.out.println("int");} static void print(RealType t){System.out.println("real");} static void print(StringType t){System.out.println("string");} } |
Zoals je ziet heeft iedere functie nu beschikking over het object waar hij eventueel allerlei data uit kan lezen of acties op uit kan voeren.
Als je nu het volgende aanroepende code zou schrijven:
Type type = new StringType();
Print.print(type)
Dan krijg je dus weer 'string' op het scherm. De reden dat er 'string' komt te staan, is dat er runtime uitgezocht wordt welke functie uitgevoerd moet worden. Dit heet ook wel dispatch: de runtime selectie van de juiste functie/methode op basis van de argument types.
In dit geval gebeurt dit op basis van 1 argument, namelijk t, dit heet dan ook wel een single dispatch. Als je 2 argumenten gebruikt om de juiste keuze te maken, dan heb je een double dispatch, en bij n argumenten is het een multidispatch. En een methode die gebruikt maakt van multidispatch heet ook wel een multimethod.
Als bij een multi/double dispatch de lexografische volgorde van argumenten belangrijk is dan spreek je van een asymetrische multidispatch en anders van een symetrische, dit gegeven is verder niet interessant voor het visitor designpattern maar volledigheidswijze vermeld ik het wel even.
Verder zou je je kunnen afvragen of de gebruikte aanroepende syntax voor deze 'externe' functies nou zo handig is. En daarnaast zal zo`n functie beschikking moeten hebben over data die je eventueel niet zou willen laten zien. Om dit probleem te verhelpen zijn 'open objects' uitgevonden. Dit zijn objecten waarop op een soortgelijke manier extern functies toegevoegd kunnen worden terwijl ze wel beschikking hebben over allerlei private informatie.
In onderstaande code geef ik een voorbeeld van symetrische double dispatched functies om te bepalen of een type een subtype is van een ander type.
code:
1
2
3
4
5
6
7
| class IsSubType{ static boolean isSubType(IntType subType, IntType superType){return true;} static boolean isSubType(IntType subType, RealType superType){return true;} static boolean isSubType(RealType subType, RealType superType){return true;} static boolean isSubType(StringType subType, StringType superType){return true;} static boolean isSubType(Type subType,Type superType){return false;} } |
Als je deze functie gaat aanroepen met isSubType(new IntType(),new RealType()), dan weet je dat de 2e functie wordt gekozen en dat je een true als antwoord krijgt. Als je aanroept met: isSubType(new StringType(),new RealType()) dan voldoet alleen de laatste functie en krijg je dus een false terug.
Ik vind persoonlijke de bovenstaande code erg mooi en dit zie je wel vaker in functionele programmeertalen onder de algemene noemer: pattern matching. (Pattern matching kan bij functionele talen vaak nog veel meer dan alleen multidispatch). En verder is het bij functionele programmeertalen gebruikelijk om een functie te maken over meerdere types en dat zie je hier ook in terug.
Er is alleen een groot probleem. Bovenstaande single en multidispatch voorbeelden zullen in talen zoals Java, c++, c#, Delphi niet werken omdat zij alleen single dispatch mbv polymorphisme bezitten en geen algemene single dispatch en al helemaal geen double of multidispatch. Daarnaast zal isSubType code ook niet gaan werken omdat java gebruik maakt van de meest ruime methode selectie en niet de meest strikte methode selectie. Aangezien de laatste methode de ruimste is (alle types kunnen daar terecht) zal deze methode altijd uitgevoerd worden en die anderen dus niet.
UITLEG VISITORS.
Wat een Visitor doet is gebruik maken van het mechanisme van polymorphistische dispatch om algemene single dispatch te verkrijgen.
Dit gebeurt mbv een callback op de volgende manier:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
| interface Visitor{ void visit(IntType t); void visit(RealType t); void visit(StringType t); } interface Type{ void accepts(Visitor v); } class IntType implements Type{ void accepts(Visitor v){v.visit(this)}; } |
Je maakt eerst een algemene visitor interface waarin alle Types staan. Je maakt het mogelijk dat een visitor ieder type kan bezoeken mbv een accepts(Visitor v) methode. En daarbij ga je het polymorphe gedrag van die methode gebruiken om die dispatch over te brengen op de visitor interface (de callback). Als je bv een IntType hebt en je gaat met een visitor op bezoek, dan zal dus de visit(IntType t) worden uitgevoerd uit de Visitor interface. Dus op basis van het type wordt de juiste methode aangeroepen op de interface. En nu hebben we dus onze algemene single dispatch.
DE DEFAULT VISIT EN ADAPTERS.
Soms is het vrij vervelend om alle methodes van een interface te moeten implementeren. Je zou dan een Adapter kunnen maken voor die interface waarin iedere methode al een lege implementatie heeft.
code:
1
2
3
4
5
| abstract class VisitorAdapter implements Visitor{ void visit(IntType t){} void visit(RealType t){} void visit(StringType t){} } |
Als je nu alleen bij een string een actie wilt ondernemen, kan je volstaan met deze code.
code:
1
2
3
| class StringTypeAction extends VisitorAdapter{ void visit(StringType t); } |
Zoals je ziet hoef je nu alleen nog maar de interessante methodes te overriden, en bij de rest wordt een lege methode aangeroepen. Het missende aan deze aanpak is dat je nu geen actie kan ondernemen op de rest. Dit zal ik verduidelijken aan de hand van het volgende voorbeeld. Stel dat je wilt bepalen of iets wel of geen getal is. Je zou dan bij de visit(IntType t) en visit(RealType t) de waarde op true kunnen zetten, en bij visit(String t) zet je hem op false. Om dit voor elkaar te krijgen zou je een default visit kunnen maken, die iedere visit methode aanroept als het niet overriden wordt.
code:
1
2
3
4
5
6
| abstract class VisitorAdapter implements Visitor{ void visit(IntType t){visitDefault(t);} void visit(RealType t){visitDefault(t);} void visit(StringType t){visitDefault(t);} void visitDefault(Type t){}; } |
code:
1
2
3
4
5
| abstract class IsNumber extends VisitorAdapter{ boolean _isNumber; void visit(StringType t){_isNumber=false;} void visitDefault(Type t){_isNumber=true;} } |
DOUBLE AND MULTIDISPATCH MBV VISITOR.
Zoals vaak ten onrechte wordt gezegd krijg je met een visitor dus geen double dispatch, maar een single dispatch. Alleen wordt dit dus gedaan aan de hand van double dispatch. Je zoekt namelijk eerst de juiste visit implementatie uit mbv polymorphisme (1e dispatch) en later wordt in de interface de juiste methode gekozen. Dit gebeurt compile time, en heet daarom ook wel een statische dispatch. Hiermee hebben we ook meteen onze 2e dispatch te pakken.
Maar je kan wel een double/multi dispatched versie maken aan de hand van meerdere visitor calls.
Voorbeeld van een double dispatched versie (ik heb de StringType er even uitgelaten ivm lengte code).
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| interface DoubleDispatchedTypeVisitor{ void visit(IntType t1, IntType t2); void visit(IntType t1, RealType t2); void visit(RealType t1, IntType t2); void visit(RealType t1, RealType t2); } class DoubleDispatcher implements TypeVisitor{ DoubleDispatchedTypeVisitor _guest;//set by constructor Type t2;//set by constructor. void visit(IntType t1){ TypeVisitor v = new TypeVisitor(){ void visit(IntType t2){_guest.visit(t1,t2);} void visit(RealType t2){_guest.visit(t1,t2);} } t2.accepts(v); } void visit(RealType t1){ TypeVisitor v = new TypeVisitor(){ void visit(IntType t2){_guest.visit(t1,t2);} void visit(RealType t2){_guest.visit(t1,t2);} } t2.accepts(v); } } class IsSubType implements DoubleDispatchedTypeVisitor{ boolean _result; void visit(IntType subType, IntType superType){_result=true;} void visit(IntType subType, RealType superType){_result=true;} void visit(RealType subType, IntType superType){_result=false;} void visit(RealType subType, RealType superType){_result=true;} } |
En aanroepen met:
Type intType = new IntType();
Type realType = new RealType();
IsSubType isSubType = new IsSubType();
intType.accepts(new DoubleDispatcher(realType,isSubType));
boolean result = isSubType.getResult();
Eventueel kan dit natuurlijk nog wel wat opgeschoond worden.
En IsSubType zou je zelfs nog kunnen vereenvoudigen mbv een algemene adapter class naar:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
| abstract class implements DoubleDispatchedTypeVisitorAdapter{ void visit(IntType t1, IntType t2){visitDefault(t1,t2);} void visit(IntType t1, RealType t2){visitDefault(t1,t2);} void visit(RealType t1, IntType t2){visitDefault(t1,t2);} void visit(RealType t1, RealType t2){visitDefault(t1,t2);} void visitDefault(Type t1, Type t2){}; } class IsSubType extends DoubleDispatchedTypeVisitorAdapter{ boolean _result; void visit(RealType subType, IntType superType){_result=false;} void visitDefault(Type t1, Type t2){_result = false;} } |
Of zelfs tot:
code:
1
2
3
4
| class IsSubType extends DoubleDispatchedTypeVisitorAdapter{ boolean _result = true; void visit(RealType subType, IntType superType){_result=false;} } |
Een voor multidispatched versie kan je nu ook wel een soorgelijke oplossing bedenken. Het probleem aan deze aanpak is alleen dat het dus enorm in de methodes kan komen te lopen, tenslotte moet je voor iedere n types en m dispatches n^m visit methodes schrijven.
VOORBEELD VISITOR IMPLEMENTATIE.
Nu we dus beschikking hebben over een single dispatch hoeven we alleen nog maar een implementatie te maken voor die Visitor interface om daar functionaleit aan te koppelen. Ik zou nu de volgende PrintVisitor kunnen maken:
code:
1
2
3
4
5
| class PrintVisitor implements Visitor{ void visit(IntType type){System.out.println("int");} void visit(RealType type){System.out.println("real");} void visit(StringType type){System.out.println("string");} } |
Als je dit nu aanroept met:
Type p = new StringType();
p.accepts(new PrintVisitor());
Dan krijg je dus 'string' op je scherm te staan.
Zoals je ziet is het dus nu erg eenvoudig om nieuwe functionaliteit aan classes toe te voegen zonder dat die classes daarvoor veranderd moeten worden. Ze hoeven dus alleen een accepts methode te hebben en later kan daar nieuwe functionliteit aan worden toegevoegd.
Om het gemak van de visitor nog wat verder te verduidelijken staan hieronder nog een aantal visitor implementaties.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class IsSubType implements Visitor{ Type _subType; boolean _result; IsSubType(Type subType){_subType = subType); void visit(IntType type){_result = _subType instanceof IntType;} void visit(RealType type){ _result = (_subType instanceof IntType) || (_result = _subType instanceof RealType); } void visit(StringType type){_result = _subType instanceof StringType} } |
VOOR EN NADELEN VISITORS.
Het is verder gebruikelijk om bij design patterns de voor en nadelen ook te vermelden en dat wil ik jullie dan ook niet onthouden.
Voordelen aan visitors:
1) je objecten blijven schoner omdat je allerlei handige functies niet in de class zelf hoeft te plaatsen.
2) je krijgt een hele mooie bundeling in 1 object van een bepaald stuk functionaliteit zodat het niet verspreid ligt over je class hierarchie.
3) in een visitor kan je allerlei toestands informatie meenemen die gewoonlijk als argument in de methode aanroep meegenomen had moet worden. Dit is vooral handig bij traversal over een structuur heen en ik zal in het volgende deel op dit punt terug komen.
Nadelen aan de visitors:
1) het kan lastig zijn om nieuwe classes aan je hierarchie toe te voegen. Stel dat je geen beschikking hebt over de visitor interface source dan kan je geen nieuwe visit methode toevoegen. In A Pattern Language To Visitors geven ze een oplossing voor dit probleem: de element adder. Als je wel beschikking hebt over de source van de Visitor interface maar andere programmeurs hebben een implementatie hiervan gemaakt. Dan kan krijg je dus in de implementatie een methode te weinig en zal je een foutmelding krijgen dat een methode niet gevonden is. Dit is verder wel een zeer vervelend probleem.
2) Als je de sources niet hebt van een class (hierarchie) dan zal is het vrijwel onmogelijk om het Visitor design pattern in te bouwen.
3) Zo nu en dan moet je ecapsulation van objecten verbreken omdat je dus nu allerlei logica buiten de bezochte objecten gaat plaatsen en je toch hele specifieke informatie nodig hebt of acties op worden ondernomen.
Ik hoop dat jullie wat opgestoken hebben van mijn verhaal want ik heb er zelf wel plezier in gehad, terwijl schrijven zeker niet eens van mijn sterke punten is. En als er genoeg interesse is zal ik binnenkort nog een hele krachtige uitbreiding op het Visitor design pattern plaatsen, namelijk 'Visitor Guides'.
Literatuur opgave:
-Design Patterns - Elements of Reusable Object Oriented Software
http://www.amazon.com/exec/obidos/tg/detail/-/0201633612
-MultiJava: Design, implementation, and evaluation of a Java-compatible language supporting modular open classes and symmetric multiple dispatch
http://www.cs.iastate.edu...tijava/papers/TR01-10.pdf
-A Pattern Language To Visitors
http://jerry.cs.uiuc.edu/...mai0/PLoP2001_ymai0_1.pdf