Utilisation du modèle de visiteur avec des génériques en C #

Je veux savoir si le schéma ci-dessous est une utilisation acceptable du modèle de visiteur. Je me sens un peu mal à l’aise de revenir d’un appel Accept () ou Visit (). S’agit-il d’une utilisation appropriée de ce modèle et si non, pourquoi pas?

Remarque: les excuses pour l’échantillon de code long, semblent nécessaires pour faire comprendre ce que je fais en tant que visiteur, semble toujours être un peu impliqué …

interface IAnimalElement { T Accept(IAnimalVisitor visitor); } interface IAnimalVisitor { T Visit(Lion lion); T Visit(Peacock peacock); T VisitZoo(List animals); } abstract class Animal { public int Age { get; protected set; } } class Lion : Animal, IAnimalElement { public Lion(int age) { Age = age; } public int Accept(IAnimalVisitor visitor) { return visitor.Visit(this); } } class Peacock : Animal, IAnimalElement { public Peacock(int age) { Age = age; } public int Accept(IAnimalVisitor visitor) { return visitor.Visit(this); } } class AnimalAgeVisitor : IAnimalVisitor { public int TotalAge { get; private set; } int IAnimalVisitor.Visit(Lion lion) { TotalAge += lion.Age; return lion.Age; } int IAnimalVisitor.Visit(Peacock peacock) { TotalAge += peacock.Age + 10; return peacock.Age + 10; // peacocks ages are always -10y, correct. } public int VisitZoo(List animals) { // Calculate average animal age. int sum = 0; int count = 0; foreach (IAnimalElement animal in animals) { sum += animal.Accept(this); ++count; } return count == 0 ? 0 : sum / count; } } class Program { static void Main(ssortingng[] args) { List animals = new List() { new Lion(10), new Lion(15), new Peacock(3), new Lion(2), new Peacock(9) }; AnimalAgeVisitor visitor = new AnimalAgeVisitor(); Console.WriteLine("Average age = {0}, Total age = {1}", visitor.VisitZoo(animals), visitor.TotalAge); } } 

Pour moi, cela donne l’impression que la mise en œuvre est un peu délicate.

Faites en sorte que vos méthodes Visit et Accept retournent la valeur null et trace tout l’état dans l’object Visiteur. Interrogez-le à la fin.

ou …

Have Visit et Accept renvoie l’état en cours et accepte de manière fonctionnelle un état en cours entrant.

Si vous optez pour la deuxième option, je ne suis pas vraiment sûr qu’un object visiteur ou un modèle est nécessaire, vous pouvez utiliser un iterator, une fonction et un état transitoire à la place.

C’est assez commun. Je ne sais pas si vous pouvez le faire en C #, mais en Java, il est normal de laisser la méthode Accept générique, donc ce qui est renvoyé est décidé par le visiteur et non par la personne visitée:

 interface IAnimalElement {  T Accept(IAnimalVisitor visitor); } interface IAnimalVisitor { T Visit(Peacock animal); ... } 

Pour les procédures, un IAnimalVisitor renvoyant la valeur null peut être utilisé.

La méthode acceptable visitable n’est pas censée renvoyer quoi que ce soit. L’accept est uniquement censé indiquer au visiteur ce qu’il faut visiter après ou pendant la visite.

Réponse courte: Je ne vois aucun problème à exposer un IVisitor renvoyant un paramètre générique.
Voir les règles de FxCop .

Il permet ensuite d’utiliser différents IVisitor renvoyant chacun une valeur différente.

Cependant, dans votre cas , Visitor n’est pas utile, car chaque animal a la propriété Age et que tout peut donc être fait avec Animal ou une nouvelle interface IAnimal .

Alternative utilise l’ envoi multiple au prix de la perte de Strong Typing .

Utilisez un modèle de visiteur lorsque vous souhaitez remplacer (ou éviter d’écrire) un commutateur comme celui-ci:

 IAnimal animal = ...; switch (animal.GetType().Name) { case "Peacock": var peacock = animal as Peacock; // Do something using the specific methods/properties of Peacock break; case "Lion": var peacock = animal as Lion; // Do something using the specific methods/properties of Lion break; etc... } 

ou l’équivalent nested if-then-else .

Son but est de router l’instance vers la routine pertinente pour son type en utilisant un polymorphism, puis d’éviter les instructions laides if-then-else / switch et les conversions manuelles . De plus, cela aide à réduire le couplage entre du code non lié.

Une autre solution consiste à append une méthode virtuelle dans l’arborescence de la classe à visiter. Cependant, parfois ce n’est pas possible ou souhaitable:

  • code de classe visitable non modifiable (non possédé par exemple)
  • code de classe visitable non lié au code de visite (l’append en classe signifierait réduire la cohésion de la classe).

C’est pourquoi il est souvent utilisé pour parcourir une arborescence d’objects (nœuds html, jetons lexer, etc …). Le modèle de visiteur implique les interfaces suivantes:

  • IVisitor

     ///  /// Interface to implement for classes visiting others. /// See Visitor design pattern for more details. ///  /// The type of the visited. /// The type of the result. public interface IVisitor : IVisitor where TVisited : IVisitable { TResult Visit(TVisited visited); } ///  /// Marking interface. ///  public interface IVisitor{} 
  • IVisitable

     ///  /// Interface to implement for classes visitable by a visitor. /// See Visitor design pattern for more details. ///  /// The type of the visitor. /// The type of the result. public interface IVisitable : IVisitable where TVisitor : IVisitor { TResult Accept(TVisitor visitor); } ///  /// Marking interface. ///  public interface IVisitable {} 

L’implémentation de Accept dans chaque IVisitable devrait appeler Visit (this) .