next up previous contents index
suivant: 5.8 Objets actifs monter: 5. Tout un monde précédent: 5.6 Clonage d'objets   Table des matières   Index


5.7 Interprétation des expressions arithmétiques

On doit représenter les expressions arithmétiques et effectuer un certain nombre de traitements sur celles-ci, par exemple, les évaluer, les imprimer de façon infixe, ou suffixe, etc. Une expression est soit une constante, soit l'addition de deux expressions, soit la multiplication de deux expressions, etc. On transcrit cette définition en une hiérarchie de types : le type parent est une interface Expr, qui est réalisée par des classes concrètes Const, Plus, Mult, etc. Supposons ces types définis ; il sera naturel de construire, par exemple, l'objet suivant :

    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();
  }

}


5.7.0.0.1 Un pattern de délégation : les visiteurs

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.


next up previous contents index
suivant: 5.8 Objets actifs monter: 5. Tout un monde précédent: 5.6 Clonage d'objets   Table des matières   Index
Rene' LALEMENT 2001-11-07