Op zondag 07 juli 2002 12:03 schreef Otis het volgende:
[..]
Het weirde eraan vind ik dat je een class moet creeeren voor 1 method, ik werk met OO heel erg data-gericht, dus data met daarop methods, en daarnaast inheritance voor het verkleinen van de hoeveelheid code door aan elkaar gerelateerde data in een hierarchie te plaatsen (en dus de methods). Het is dan voor mij een beetje een rare constructie dat je om functies te gebruiken aparte classes moet aanmaken

Eigelijk voelt het heel natuurlijk aan, alhoewel het in het begin inderdaad vreemd is. Maar ik beschouw mijn hierarchie nu als een data dragen geheel, en het heeft zelf geen functionaliteit meer in zich (afgezien van consistent houden). Hierdoor blijven mijn classes veel schoner, en alle functionaliteit bv: bepaal totaal lengte hout, kan mbv een visitor worden bepaald.
Doordat je gebruikt maakt van zo`n visitor heb je niet bij ieder object een 'calcTotaalHoeveelheidHout' staan en later: 'calcTotaalOpp' 'calcInhoud' 'calcGewicht' etc methodes. Vooral als er meer functionaliteit in moet, dan kan zo`n hierarchie echt een drama worden. Ik heb vorig jaar een stuk software geschreven voor een timmerfabriek en ik heb het op de ouderwetse manier gehaad. Op den duur zie je door het bomen het bos niet meer, maar ik ga hem deze week even te grazen nemen.
Het visitor design pattern wordt geintroduceerd, en ik ga nu gebruik maken van hele simpele model objecten (string, ing, bool etc) met ingebouwde controllers (en verhindering van memory leaks door een speciale constructie!!!) en events om standaard gui objecten (labels,textfields ed) daarop aan te sluiten. (ben op dit moment nog aan het experimenteren ermee, dus wat hieronder staat is alleen een probeersel).
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
| public class Persoon{
private StringModel<Persoon> _voornaamM = new StringModel<Persoon>(this);
private StringModel<Persoon> _achternaamM = new StringModel<Persoon>(this);
private StringModel<Persoon> _woonPlaatsM = new StringModel<Persoon>(this);
public Persoon(String voornaam,String achternaam,String woonplaats){
_voornaamM.setValue(voornaam);
_achternaamM.setValue(achternaam);
_woonPlaatsM.setValue(woonplaats);
}
public StringModel<Persoon> getAchternaamM(){
return _achternaamM;
}
public StringModel<Persoon> getVoornaamM(){
return _voornaamM;
}
public StringModel<Persoon> getWoonplaatsM(){
return _woonPlaatsM;
}
} |
aansluiten:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public class TestPanel extends JPanel
{
private Persoon _persoon = new Persoon("jan","bakker","slochter");
public TestPanel(){
super(new BorderLayout());
add(new StringLabel(_persoon.getVoornaamM()),BorderLayout.NORTH);
add(new StringTextField(_persoon.getAchternaamM()),BorderLayout.CENTER);
add(new StringLabel(_persoon.getAchternaamM()),BorderLayout.SOUTH);
}
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setContentPane(new TestPanel());
frame.setSize(300,300);
frame.setVisible(true);
}
} |
Hierdoor hoef ik geen gui code meer aan te sluiten op een controller, en kan dus dus veel handiger werken.
Visitor design pattern:
http://exciton.cs.oberlin.edu/javaresources/DesignPatterns/VisitorPattern.htm
Ik moest hier echt een paar keer naar kijken, maar het is inderdaad vrij slim gedaan.
Allebei wel zaken waar ik niet 1 2 3 aan zou denken eerlijk gezegd, maar in sommige gevallen wel handig. Het bezwaar dat ik er tegen heb, en wat de 'static' inheritance methode wel levert, is de leesbaarheid van de code (als ik het goed begrijp, en na een aantal keren met mn duffe hoofd de tekstoverlezende kan ik wel indenken dat je er solide software mee kunt bouwen). De lezer, dus de mens, moet onthouden welk algo gepassed wordt naar het object, dus tijdens het lezen, en bv het nakijken van de code, kun je dus de bugs over het hoofd zien dat je het verkeerde algo passed. Maar een geoefende programmeur zal dit ongetwijfeld wel correct doen, het lijkt mij alleen een stukje complexer worden tijdens het lezen van de sourcecode (en IMHO dan foutgevoeliger juist).
De source code wordt juist niet foutgevoeliger. Je moet bij iedere class in je hierarchie maar 1 methode schrijven:
code:
1
2
3
4
5
6
| public void accepts(FruitVisitor visitor){
if(visitor == null){
... foutmelding
}
visitor.visitPeer(this);
} |
Dit kan je eigelijk niet fout doen, verder kun je nu al je handige functies uit de hierarchie gaan verwijderen. (Die hierarchie moet echt behoorlijk dom worden). De hierarchie is nu geschikt gemaakt voor het ontvangen van een visitor.
implementatie van een visitor
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public class FruitOpeter implements FruitVisitor{
public void visitBanaan(Banaan fruit){
fruit.haalBanaaneSchilEraf();
fruit.eet();
}
public void visitPeer(Peer fruit){
fruit.eet();
}
public void visitSinasAppel(SinasAppel fruit){
fruit.schilSinasappel();
fruit.eet();
}
} |
Nu heb je dus in 1 bestand alle functionaliteit mbt het eten van fruit, ipv dat het over een hele hierarchie ligt verspreid. Eigelijk kan je dit wel een beetje onder de noemer AOP (Aspect Oriented Programming) gooien, omdat je iedere keer een bepaald aspect van het probleem gaat behandelen.
Generics maken IMHO het visitor design pattern voor een deel overbodig, heb ik dat goed?
Je kan met het visitor design pattern nog veel meer doen. De kracht van het visitor design pattern is dat je achteraf functionaliteit kan toevoegen aan een bepaalde object hierarchie, maar ook dat je dus allerlei traversal strategieen erop kan uitvoeren. Je hoeft dus zelf niet meer door een object hierarchie te gaan wandelen, vb: stel dat ik een expressie wil afdrukken, dan zou ik deze visitor kunnen maken.
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class PrintVisitor extends ExprVisitor{
public void visitVariable(Variable var){
System.out.prinltn(var.getName());
}
public void visitInteger(Integer integer){
System.out.println(integer.getValue());
}
public void visitAdd(AddOperator addOperator){
System.out.println("+");
}
public void visitSub(SubOperator subOperator){
System.out.println("-");
}
} |
En ik zou hem als volgt kunnen afdrukken.
expression.accepts(new InfixVisitor(new PrintVisitor());
expression.accepts(new PostFixVisitor(new PrintVisitor());
expression.accepts(new PreFixVisitor(new PrintVisitor());
Je ziet nu dat je ook een bepaalde classes hebt die verantwoordelijk is voor het bewandelen van die hierarchie, namelijk Infix,Prefix en PostFix visitor. Deze 'guides' kan je iedere keer hergebruiken, en dat maakt je code ook een stug bugvrijer.
Het visitor design pattern is eerlijk gezegd de beste design pattern die ik ben tegen gekomen, maar uit de voorbeelden in de boeken komt hij totaal niet tot zijn recht. Je kan er zoveel mee doen. Er is ook een generieke traversal library JJTraveler waarmee je zelfs die guides niet meer hoeft te maken.