Le motif du visiteur avec des exemples

Je suis vraiment confus au sujet du modèle de visiteur et de ses utilisations. Je ne peux pas vraiment visualiser les avantages de l’utilisation de ce modèle ou de son objective. Si quelqu’un pouvait expliquer avec des exemples si possible, ce serait génial.

    Il était une fois…

    class MusicLibrary { private Set collection ... public Set getPopMusic() { ... } public Set getRockMusic() { ... } public Set getElectronicaMusic() { ... } } 

    Ensuite, vous réalisez que vous souhaitez pouvoir filtrer la collection de la bibliothèque par d’autres genres. Vous pouvez continuer à append de nouvelles méthodes de lecture. Ou vous pourriez utiliser les visiteurs.

     interface Visitor { visit(Set items); } interface MusicVisitor extends Visitor; class MusicLibrary { private Set collection ... public void accept(MusicVisitor visitor) { visitor.visit( this.collection ); } } class RockMusicVisitor implements MusicVisitor { private final Set picks = ... public visit(Set items) { ... } public Set getRockMusic() { return this.picks; } } class AmbientMusicVisitor implements MusicVisitor { private final Set picks = ... public visit(Set items) { ... } public Set getAmbientMusic() { return this.picks; } } 

    Vous séparez les données de l’algorithme. Vous déchargez l’algorithme dans les implémentations des visiteurs. Vous ajoutez des fonctionnalités en créant plus de visiteurs, au lieu de modifier (et de gonfler) constamment la classe contenant les données.

    Donc, vous avez probablement lu des explications bajillion du modèle de visiteur, et vous êtes probablement encore en train de dire “mais quand l’utiliseriez-vous!”

    Traditionnellement, les visiteurs ont l’habitude de mettre en œuvre des tests de type sans sacrifier la sécurité de type, à condition que vos types soient bien définis et connus à l’avance. Disons que nous avons quelques classes comme suit:

     abstract class Fruit { } class Orange : Fruit { } class Apple : Fruit { } class Banana : Fruit { } 

    Et disons que nous créons un Fruit[] :

     var fruits = new Fruit[] { new Orange(), new Apple(), new Banana(), new Banana(), new Banana(), new Orange() }; 

    Je veux partitionner la liste dans les trois listes, chacune contenant des oranges, des pommes ou des bananes. Comment le feriez-vous? Eh bien, la solution simple serait un test de type:

     List oranges = new List(); List apples = new List(); List bananas = new List(); foreach (Fruit fruit in fruits) { if (fruit is Orange) oranges.Add((Orange)fruit); else if (fruit is Apple) apples.Add((Apple)fruit); else if (fruit is Banana) bananas.Add((Banana)fruit); } 

    Cela fonctionne, mais il y a beaucoup de problèmes avec ce code:

    • Pour commencer, c’est moche.
    • Ce n’est pas sûr, nous n’accepterons pas les erreurs de type avant l’exécution.
    • Ce n’est pas maintenable. Si nous ajoutons une nouvelle instance dérivée de Fruit, nous devons effectuer une recherche globale pour chaque endroit effectuant un test de type fruit, sinon nous pourrions manquer des types.

    Le motif du visiteur résout le problème avec élégance. Commencez par modifier notre classe de base Fruit:

     interface IFruitVisitor { void Visit(Orange fruit); void Visit(Apple fruit); void Visit(Banana fruit); } abstract class Fruit { public abstract void Accept(IFruitVisitor visitor); } class Orange : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } class Apple : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } class Banana : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } } 

    Il semble que nous ayons copié le code, mais notez que les classes dérivées appellent toutes des surcharges différentes (les appels d’ Apple Visit(Apple) , les appels Banana Visit(Banana) , etc.).

    Implémenter le visiteur:

     class FruitPartitioner : IFruitVisitor { public List Oranges { get; private set; } public List Apples { get; private set; } public List Bananas { get; private set; } public FruitPartitioner() { Oranges = new List(); Apples = new List(); Bananas = new List(); } public void Visit(Orange fruit) { Oranges.Add(fruit); } public void Visit(Apple fruit) { Apples.Add(fruit); } public void Visit(Banana fruit) { Bananas.Add(fruit); } } 

    Maintenant, vous pouvez partitionner vos fruits sans test de type:

     FruitPartitioner partitioner = new FruitPartitioner(); foreach (Fruit fruit in fruits) { fruit.Accept(partitioner); } Console.WriteLine("Oranges.Count: {0}", partitioner.Oranges.Count); Console.WriteLine("Apples.Count: {0}", partitioner.Apples.Count); Console.WriteLine("Bananas.Count: {0}", partitioner.Bananas.Count); 

    Cela présente les avantages de:

    • Étant relativement propre, facile à lire le code.
    • La sécurité de type, les erreurs de type sont sockets au moment de la compilation.
    • Maintenabilité. Si j’ajoute ou supprime une classe Fruit concrète, je pourrais modifier mon interface IFruitVisitor pour gérer le type en conséquence, et le compilateur trouvera immédiatement tous les endroits où nous implémenterons l’interface afin que nous puissions apporter les modifications appropriées.

    Cela dit, les visiteurs sont généralement débordés et ils ont tendance à compliquer grossièrement les API, et il peut être très compliqué de définir un nouveau visiteur pour chaque nouveau type de comportement.

    Généralement, des modèles plus simples, tels que l’inheritance, devraient être utilisés à la place des visiteurs. Par exemple, en principe, je pourrais écrire une classe comme:

     class FruitPricer : IFruitVisitor { public double Price { get; private set; } public void Visit(Orange fruit) { Price = 0.69; } public void Visit(Apple fruit) { Price = 0.89; } public void Visit(Banana fruit) { Price = 1.11; } } 

    Cela fonctionne, mais quel est l’avantage par rapport à cette modification sortingviale:

     abstract class Fruit { public abstract void Accept(IFruitVisitor visitor); public abstract double Price { get; } } 

    Vous devez donc utiliser les visiteurs lorsque les conditions suivantes sont remplies:

    • Vous avez un ensemble de classes bien défini et connu qui sera visité.

    • Les opérations sur lesdites classes ne sont pas bien définies ou connues à l’avance. Par exemple, si quelqu’un consum votre API et que vous souhaitez donner aux consommateurs un moyen d’append de nouvelles fonctionnalités ad hoc aux objects. Ils sont également un moyen pratique d’étendre les classes scellées avec une fonctionnalité ad hoc.

    • Vous effectuez les opérations d’une classe d’objects et souhaitez éviter les tests de type à l’exécution. C’est généralement le cas lorsque vous parcourez une hiérarchie d’objects disparates ayant des propriétés différentes.

    N’utilisez pas de visiteurs lorsque:

    • Vous prenez en charge les opérations sur une classe d’objects dont les types dérivés ne sont pas connus à l’avance.

    • Les opérations sur les objects sont bien définies à l’avance, en particulier si elles peuvent être héritées d’une classe de base ou définies dans une interface.

    • Il est plus facile pour les clients d’append de nouvelles fonctionnalités aux classes utilisant l’inheritance.

    • Vous parcourez une hiérarchie d’objects ayant les mêmes propriétés ou interfaces.

    • Vous voulez une API relativement simple.

    Il fournit une autre couche d’abstraction. Réduit la complexité d’un object et le rend plus modulaire. Par exemple, utiliser une interface (l’implémentation étant totalement indépendante et personne ne se soucie de la façon dont cela se fait).

    Maintenant je ne l’ai jamais utilisé mais ce serait utile pour: Implémenter une fonction particulière qui doit être faite dans différentes sous-classes, puisque chacune des sous-classes doit l’implémenter de différentes manières qu’une autre classe implémenterait toutes les fonctions. Un peu comme un module mais seulement pour un ensemble de classes. Wikipedia a une bonne explication: http://en.wikipedia.org/wiki/Visitor_pattern Et leur exemple aide à expliquer ce que j’essaie de dire.

    J’espère que ça aide à clarifier un peu.

    EDIT ** Désolé j’ai lié à Wikipedia pour votre réponse mais ils ont vraiment un bon exemple 🙂 Ne pas essayer d’être ce gars qui dit aller le trouver vous-même.

    Exemple de modèle de visiteur. Book, Fruit & Vegetable sont des éléments de base de type “Visitable” et il y a deux “Visitors” , BillingVisitor & OfferVisitor, chacun des visiteurs a son propre but .Algo pour calculer la facture et algo pour calculer les offres sur ces éléments est encapsulé dans le visiteur respectif et les visitables (éléments) restnt les mêmes.

     import java.util.ArrayList; import java.util.List; public class VisitorPattern { public static void main(Ssortingng[] args) { List visitableElements = new ArrayList(); visitableElements.add(new Book("I123",10,2.0)); visitableElements.add(new Fruit(5,7.0)); visitableElements.add(new Vegetable(25,8.0)); BillingVisitor billingVisitor = new BillingVisitor(); for(Visitable visitableElement : visitableElements){ visitableElement.accept(billingVisitor); } OfferVisitor offerVisitor = new OfferVisitor(); for(Visitable visitableElement : visitableElements){ visitableElement.accept(offerVisitor); } System.out.println("Total bill " + billingVisitor.totalPrice); System.out.println("Offer " + offerVisitor.offer); } interface Visitor { void visit(Book book); void visit(Vegetable vegetable); void visit(Fruit fruit); } //Element interface Visitable{ public void accept(Visitor visitor); } static class OfferVisitor implements Visitor{ SsortingngBuilder offer = new SsortingngBuilder(); @Override public void visit(Book book) { offer.append("Book " + book.isbn + " discount 10 %" + " \n"); } @Override public void visit(Vegetable vegetable) { offer.append("Vegetable No discount \n"); } @Override public void visit(Fruit fruit) { offer.append("Fruits No discount \n"); } } static class BillingVisitor implements Visitor{ double totalPrice = 0.0; @Override public void visit(Book book) { totalPrice += (book.quantity * book.price); } @Override public void visit(Vegetable vegetable) { totalPrice += (vegetable.weight * vegetable.price); } @Override public void visit(Fruit fruit) { totalPrice += (fruit.quantity * fruit.price); } } static class Book implements Visitable{ private Ssortingng isbn; private double quantity; private double price; public Book(Ssortingng isbn, double quantity, double price) { this.isbn = isbn; this.quantity = quantity; this.price = price; } @Override public void accept(Visitor visitor) { visitor.visit(this); } } static class Fruit implements Visitable{ private double quantity; private double price; public Fruit(double quantity, double price) { this.quantity = quantity; this.price = price; } @Override public void accept(Visitor visitor) { visitor.visit(this); } } static class Vegetable implements Visitable{ private double weight; private double price; public Vegetable(double weight, double price) { this.weight = weight; this.price = price; } @Override public void accept(Visitor visitor) { visitor.visit(this); } } } 

    Je pense que l’objective principal du modèle de visiteur est qu’il est très extensible. L’intuition est que vous avez acheté un robot. Le robot a déjà complètement implémenté les fonctionnalités élémentaires comme aller de l’avant, tourner à gauche, tourner à droite, reculer, ramasser quelque chose, parler une phase,…

    Un jour, vous voulez que votre robot puisse aller au bureau de poste pour vous. Avec toutes ces fonctionnalités élémentaires, il peut le faire, mais vous devez apporter votre robot au magasin et “mettre à jour” votre robot. Le vendeur n’a pas besoin de modifier le robot, mais il suffit de mettre une nouvelle puce de mise à jour sur votre robot et il peut faire ce que vous voulez.

    Un autre jour, vous voulez que votre robot se rende au supermarché. Même processus, vous devez apporter votre robot à la boutique et mettre à jour cette fonctionnalité “avancée”. Pas besoin de modifier le robot lui-même.

    etc …

    Donc, l’idée du modèle de visiteur est que, étant donné toutes les fonctionnalités élémentaires implémentées, vous pouvez utiliser le modèle de visiteur pour append un nombre infini de fonctionnalités sophistiquées. Dans l’exemple, le robot est vos classes de travail et la “puce de mise à jour” sont des visiteurs. Chaque fois que vous avez besoin d’une nouvelle “mise à jour” des fonctionnalités, vous ne modifiez pas votre classe de travail, mais vous ajoutez un visiteur.

    Il s’agit de séparer la manipulation des données des données réelles. En prime, vous pouvez réutiliser la même classe de visiteurs pour l’ensemble de la hiérarchie de vos classes, ce qui vous évite encore de manipuler les algorithmes de manipulation de données qui ne sont pas pertinents pour vos objects réels.