Expr expr = // expr = 2 + (3+6) new Plus(new Const(2), new Plus(new Const(3), new Const(6)));
Les classes concrètes Const, Plus, etc., sont des réalisations de l'interface Expr :
interface Expr { ... } class Const implements Expr { private int c; Const(int c) { this.c = c; } ... } class Plus implements Expr { private Expr expr1, expr2; Plus(Expr expr1, Expr expr2) { this.expr1 = expr1; this.expr2 = expr2; } ... }
Une façon de procéder est de déclarer des méthodes d'évaluation (éval) et d'affichage (infixe) dans l'interface Expr et de les définir dans ses réalisations.
Expr expr = ... // expr = 2 + (3+6) Object valeur = expr.éval(); // valeur = 11 Object chaine = expr.infixe(); // chaine = "2+(3+6)"
interface Expr { int éval(); String infixe(); } class Const implements Expr { // ... public int éval() { return c; } public String infixe() { return new String(c); } } class Plus implements Expr { // ... public int éval() { return expr1.éval() + expr2.éval(); } public String infixe() { return expr1.infixe() + " + " + expr2.infixe(); } }
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.