Une autre façon est de déléguer ces fonctions à un autre objet, appelé visiteur. On introduit donc une interface pour typer ces visiteurs d'expression, avec autant de méthodes que de classes concrètes d'expression :
interface ExprVisiteur { Object visiterConst(int c); Object visiterPlus(Expr expr1, Expr expr2); }
Chacune de ces méthodes doit traiter une instance d'une classe d'expressions et produire un objet. Il y aura autant de réalisations de cette interface que de traitements demandés :
class EvalVisiteur implements ExprVisiteur { ... } class InfixeVisiteur implements ExprVisiteur { ... }
La classe Expr n'a plus besoin que d'une unique méthode pour associer un de ses objets à un traitement. On appelle cette méthode déléguer, car l'expression « délègue » le traitement à un visiteur, et on l'utilise ainsi :
Expr expr = ... // expr = 2 + (3+6) ExprVisiteur éval = new EvalVisiteur(); ExprVisiteur infixe = new InfixeVisiteur(); Object valeur = expr.déléguer(éval); // valeur = 11 Object chaine = expr.déléguer(infixe); // chaine = "2+(3+6)"
Ainsi, les données (les expressions) et les traitements (évaluations, etc.) sur ces données sont découplés en deux objets distincts. On doit donc déclarer une méthode dans l'interface Expr :
interface Expr { Object déléguer(ExprVisiteur v); }
Il faut la définir dans chaque classe d'expressions, en appelant la méthode du visiteur spéciale à cette classe, avec les bons arguments :
class Const implements Expr { private int c; Const(int c) { this.c = c; } public Object déléguer(ExprVisiteur v) { return v.visiterConst(c); } } class Plus implements Expr { private Expr expr1, expr2; Plus(Expr expr1, Expr expr2) { this.expr1 = expr1; this.expr2 = expr2; } public Object déléguer(ExprVisiteur v) { return v.visiterPlus(expr1, expr2); } }
Il reste à définir les méthodes des visiteurs, pour chaque classe d'expression et pour chaque traitement demandé. Comme tous les traitements doivent retourner un Object, l'évaluation retourne un Integer (pas un int!) et l'impression retourne un String, qui sont des Objects :
class EvalVisiteur implements ExprVisiteur { public Object visiterConst(int c) { return new Integer(c); } public Object visiterPlus(Expr expr1, Expr expr2) { return new Integer(((Integer)expr1.déléguer(this)).intValue() + ((Integer)expr2.déléguer(this)).intValue()); } } class InfixeVisiteur implements ExprVisiteur { public Object visiterConst(int c) { return Integer.toString(c); } public Object visiterPlus(Expr expr1, Expr expr2) { return "(" + expr1.déléguer(this) + "+" + expr2.déléguer(this) + ")"; } }
Les données et les traitements étant découplés, si un nouveau traitement doit être programmé, il suffit d'écrire une nouvelle classe réalisant ExprVisiteur, sans toucher aux autres ni toucher aux différentes classes d'expressions. Si par contre, on ajoute une nouvelle classe d'expressions (les produits, divisions, etc.), il faut aussi ajouter une méthode pour cette classe dans chacune des classes concrètes de visiteurs.