Utilisation d'un modèle de visiteur avec une grande hiérarchie d'objets

12

Le contexte

J'ai utilisé avec une hiérarchie d'objets (une arborescence d'expression) un modèle de visiteur "pseudo" (pseudo, car il n'utilise pas la double répartition):

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

Cette conception était cependant discutable, assez confortable car le nombre d'implémentations de MyInterface est important (~ 50 ou plus) et je n'avais pas besoin d'ajouter d'opérations supplémentaires.

Chaque implémentation est unique (c'est une expression ou un opérateur différent), et certains sont des composites (c'est-à-dire des nœuds d'opérateur qui contiendront d'autres nœuds d'opérateur / feuille).

La traversée est actuellement effectuée en appelant l'opération Accept sur le nœud racine de l'arborescence, qui à son tour appelle Accept sur chacun de ses nœuds enfants, qui à leur tour ... et ainsi de suite ...

Mais le moment est venu où j'ai besoin d' ajouter une nouvelle opération , comme une jolie impression:

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

Je vois essentiellement deux options:

  • Gardez le même design, en ajoutant une nouvelle méthode pour mon opération à chaque classe dérivée, au détriment de la maintenabilité (pas une option, à mon humble avis)
  • Utilisez le "vrai" modèle de visiteur, au détriment de l'extensibilité (pas une option, car je m'attends à avoir plus d'implémentations à venir ...), avec environ 50+ surcharges de la méthode Visit, chacune correspondant à une implémentation spécifique ?

Question

Recommanderiez-vous d'utiliser le modèle Visitor? Y a-t-il un autre modèle qui pourrait aider à résoudre ce problème?

T. Fabre
la source
1
Peut-être qu'une chaîne de décorateurs serait plus appropriée?
MattDavey
quelques questions: comment ces implémentations diffèrent-elles? quelle est la structure de la hiérarchie? et est-ce toujours la même structure? avez-vous toujours besoin de parcourir la structure dans le même ordre?
jk.
@MattDavey: vous recommanderiez donc d'avoir un décorateur par implémentation et opération?
T. Fabre
2
@ T.Fabre c'est difficile à dire. Il y a plus de 50 implémenteurs de MyInterface.. toutes ces classes ont-elles une implémentation unique de DoSomethinget DoSomethingElse? Je ne vois pas où votre classe de visiteurs traverse réellement la hiérarchie - cela ressemble plus à un facadepour le moment ..
MattDavey
aussi quelle version de C # est-ce. avez-vous des lambdas? ou linq? à votre disposition
jk.

Réponses:

13

J'ai utilisé le modèle de visiteur pour représenter des arbres d'expression au cours de plus de 10 ans sur six projets à grande échelle dans trois langages de programmation, et je suis très heureux du résultat. J'ai trouvé quelques choses qui ont rendu l'application du modèle beaucoup plus facile:

N'utilisez pas de surcharges dans l'interface du visiteur

Mettez le type dans le nom de la méthode, c'est-à-dire utilisez

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

plutôt que

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

Ajoutez une méthode "catch unknown" à votre interface visiteur.

Cela permettrait aux utilisateurs qui ne peuvent pas modifier votre code:

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

Cela leur permettrait de construire leurs propres implémentations de IExpressionet IVisitorqui "comprend" leurs expressions en utilisant des informations de type d'exécution dans l'implémentation de leur VisitExpressionméthode fourre-tout .

Fournir une implémentation par défaut de l' IVisitorinterface

Cela permettrait aux utilisateurs qui ont besoin de gérer un sous-ensemble de types d'expression de construire plus rapidement leurs visiteurs et de rendre leur code à l'abri de l'ajout de méthodes IVisitor. Par exemple, écrire un visiteur qui récolte tous les noms de variables de vos expressions devient une tâche facile, et le code ne se cassera pas même si vous ajoutez un tas de nouveaux types d'expression à votre IVisitorplus tard.

dasblinkenlight
la source
2
Pouvez-vous préciser pourquoi vous dites Do not use overloads in the interface of the visitor?
Steven Evers
1
Pouvez-vous expliquer pourquoi vous ne recommandez pas d'utiliser des surcharges? J'ai lu quelque part (sur oodesign.com, en fait) que cela n'a pas vraiment d'importance que j'utilise des surcharges ou non. Y a-t-il une raison particulière pour laquelle vous préférez ce design?
T. Fabre
2
@ T.Fabre Peu importe en termes de vitesse, mais cela importe en termes de lisibilité. La résolution de méthode dans deux des trois langages dans lesquels j'ai implémenté cela ( Java et C #) nécessite une étape d'exécution pour choisir parmi les surcharges potentielles, ce qui rend le code avec un nombre massif de surcharges un peu plus difficile à lire. La refactorisation du code devient également plus facile, car le choix de la méthode que vous souhaitez modifier devient une tâche triviale.
dasblinkenlight
@SnOrfus Veuillez voir ma réponse à T.Fabre ci-dessus.
dasblinkenlight
@dasblinkenlight C # offre désormais une dynamique permettant au runtime de décider quelle méthode surchargée doit être utilisée (pas au moment de la compilation). Y a-t-il encore une raison de ne pas utiliser la surcharge?
Tintenfiisch