Quand dois-je utiliser le motif du visiteur?

je continue à voir des références au modèle de visiteur dans les blogs, mais je dois admettre, Je ne comprends pas. J'ai lu l'article sur wikipedia pour le modèle et je comprends sa mécanique, mais je suis encore confus quant au moment où je l'utiliserais.

comme quelqu'un qui tout récemment vraiment obtenu le motif décorateur et voit maintenant des utilisations pour elle absolument partout, je voudrais être en mesure de vraiment comprendre intuitivement ce apparemment pratique modèle.

273
demandé sur Ravindra babu 2008-11-01 02:04:18

20 réponses

Je ne suis pas très familier avec le profil des visiteurs. Voyons voir si j'ai bien compris. Supposons que vous ayez une hiérarchie d'animaux

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(supposons qu'il s'agisse d'une hiérarchie complexe avec une interface bien établie.)

maintenant nous voulons ajouter une nouvelle opération à la hiérarchie, à savoir nous voulons que chaque animal fasse son son. Dans la mesure où la hiérarchie est aussi simple, vous pouvez le faire avec le polymorphisme simple:

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

But cette façon de procéder, chaque fois que vous souhaitez ajouter une opération, vous devez modifier l'interface pour chaque classe de la hiérarchie. Supposons maintenant que vous soyez satisfait de l'interface d'origine et que vous vouliez y apporter le moins de modifications possible.

le motif visiteur vous permet de déplacer chaque nouvelle opération dans une classe appropriée, et vous avez besoin d'étendre l'interface de la hiérarchie qu'une seule fois. Let's do it. Tout d'abord, nous définissons une opération abstraite (la Classe "visiteur" en GoF) qui a une méthode pour chaque classe de la hiérarchie:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

ensuite, nous modifions la hiérarchie pour accepter de nouvelles opérations:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

enfin, nous mettons en œuvre l'opération réelle, sans modifier ni le chat ni le chien :

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

Maintenant vous avez un moyen d'ajouter des opérations sans modifier la hiérarchie plus. Voici comment cela fonctionne:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}
279
répondu Federico A. Ramponi 2012-02-15 11:32:58

la raison de votre confusion est probablement que le visiteur est un nom fatal. Beaucoup (proéminent 1 !) les programmeurs ont trébuché sur ce problème. Ce qu'il fait réellement est d'implémenter double dispatching dans des langues qui ne le supportent pas nativement (la plupart d'entre eux ne le font pas).


1) mon exemple préféré est Scott Meyers, l'auteur acclamé de " Effective C++", qui a appelé celui-ci de son le plus important C++ aha! des moments jamais .

113
répondu Konrad Rudolph 2008-10-31 23:09:36

tout le monde ici a raison, mais je pense qu'il n'aborde pas le "quand". Tout d'abord, de dessins:

visiteur vous permet de définir un nouveau fonctionnement sans changement de classe des éléments sur lesquels il opère.

pensons maintenant à une simple hiérarchie de classe. J'ai des classes 1, 2, 3 et 4 et les méthodes A, B, C et D. les agencer comme dans une feuille de calcul: les classes sont les lignes et les méthodes sont les colonnes.

maintenant, le design orienté objet suppose que vous êtes plus susceptible de développer de nouvelles classes que de nouvelles méthodes, donc ajouter plus de lignes, pour ainsi dire, est plus facile. Vous ajoutez juste une nouvelle classe, spécifiez ce qui est différent dans cette classe, et hérite du reste.

parfois, cependant, les classes sont relativement statiques, mais vous devez ajouter plus de méthodes fréquemment -- ajout de colonnes. La méthode standard dans une conception OO serait d'ajouter de telles méthodes à toutes les classes, qui peuvent être coûteux. Le motif des visiteurs rend cela facile.

soit dit en passant, C'est le problème que Scala cherche à résoudre.

73
répondu Daniel C. Sobral 2009-01-26 02:00:56

Le Visiteur modèle de conception fonctionne vraiment bien pour "récursive" des structures comme le répertoire des arbres, des structures XML, ou document décrit en détail.

un objet visiteur visite chaque noeud de la structure récursive: chaque répertoire, chaque balise XML, peu importe. L'objet visiteur ne fait pas de boucle à travers la structure. Au lieu de cela les méthodes de visiteur sont appliquées à chaque noeud de la structure.

voici un noeud récursif typique structure. Peut être un répertoire ou une balise XML. [Si votre Java personne, imaginer de beaucoup de méthodes pour construire et maintenir la liste des enfants.]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

la méthode visit applique un objet visiteur à chaque noeud de la structure. Dans ce cas, c'est un visiteur descendant. Vous pouvez modifier la structure de la méthode visit pour effectuer une commande ascendante ou autre.

Voici une superclasse pour les visiteurs. Il est utilisé par le visit la méthode. Il "arrive" à chaque noeud de la structure. Depuis les appels de la méthode visit up et down , le visiteur peut garder une trace de la profondeur.

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

une sous-classe pourrait faire des choses comme compter les noeuds à chaque niveau et accumuler une liste de noeuds, générant un beau chemin des numéros de section hiérarchiques.

Voici une application. Il construit une structure arborescente, someTree . Il crée un Visitor , dumpNodes .

Puis il applique le dumpNodes à l'arbre. L'objet dumpNode "visitera" chaque noeud de l'arbre.

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

l'algorithme TreeNode visit assurera que chaque TreeNode est utilisé comme argument à la méthode arrivedAt du visiteur.

19
répondu S.Lott 2008-11-01 02:44:57

une façon de le regarder est que le modèle de visiteur est une façon de laisser vos clients ajouter des méthodes supplémentaires à toutes vos classes dans une hiérarchie de classe particulière.

il est utile quand vous avez une hiérarchie de classe assez stable, mais vous avez des exigences changeantes de ce qui doit être fait avec cette hiérarchie.

l'exemple classique est pour les compilateurs et similaires. Un arbre de syntaxe abstraite (AST) peut définir avec précision la structure de le langage de programmation, mais les opérations que vous pourriez vouloir faire sur L'AST changeront au fur et à mesure que votre projet avance: générateurs de code, jolies-imprimantes, débogueurs, analyse des paramètres de complexité.

sans le motif visiteur, chaque fois qu'un développeur voulait ajouter une nouvelle fonctionnalité, il devait ajouter cette méthode à chaque fonctionnalité de la classe de base. Cela est particulièrement difficile lorsque les classes de base apparaissent dans une bibliothèque séparée, ou sont produites par une autre équipe.

(j'ai entendu dire que le profil des visiteurs est en conflit avec les bonnes pratiques D'OO, parce qu'il déplace les opérations des données loin des données. Le modèle de visiteur est utile précisément dans la situation où les pratiques normales d'OO échouent.)

16
répondu Oddthinking 2008-10-31 23:13:05

il y a au moins trois très bonnes raisons d'utiliser le profil des visiteurs:

  1. réduire la prolifération de code qui n'est que légèrement différente lorsque la structure des données change.

  2. appliquer le même calcul à plusieurs structures de données, sans changer le code qui met en œuvre le calcul.

  3. ajouter de l'information aux bibliothèques patrimoniales sans changer le code d'héritage.

consultez un article que j'ai écrit à propos de cette .

13
répondu Richard Gomes 2013-09-21 12:06:38

comme Konrad Rudolph déjà souligné, il est adapté pour les cas où nous avons besoin Double expédition

voici un exemple pour montrer une situation où nous avons besoin d'une double répartition et comment visiteur nous aide à le faire.

exemple:

disons que j'ai 3 types d'appareils mobiles - iPhone, Android, Windows Mobile.

tous ces trois dispositifs ont un Radio Bluetooth installée dans eux.

laisse supposer que la radio de la dent bleue peut être de 2 OEMs distincts – Intel & Broadcom.

juste pour rendre l'exemple pertinent pour notre discussion, supposons aussi que les expositions APIs par Intel radio sont différentes de celles exposées par Broadcom radio.

voici à quoi ressemblent mes cours -

enter image description here enter image description here

maintenant, je voudrais introduire une opération – commutation sur le Bluetooth sur l'appareil mobile.

sa fonction signature devrait ressembler à quelque chose comme ceci -

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

ainsi, selon type droit de l'appareil et selon le type droit de radio Bluetooth , il peut être allumé par appelant les étapes appropriées ou l'algorithme de .

en principe, il devient une matrice 3 x 2, où-dans j'essaie de vectorier la bonne opération en fonction du bon type d'objets impliqués.

un comportement polymorphe dépendant du type des deux arguments.

enter image description here

maintenant, le motif de visiteur peut être appliqué à ce problème. L'Inspiration vient de la Wikipédia page indiquant – "En substance, le visiteur permet d'ajouter de nouvelles fonctions virtuelles à une famille de classes sans modifier les classes elles-mêmes; au lieu de cela, on crée un visiteur classe qui implémente toutes les spécialisations de la fonction virtuelle. Le visiteur prend la référence d'instance comme entrée, et met en œuvre le but par une double expédition."

Double expédition est une nécessité en raison de la matrice 3x2

voici à quoi ressemblera l'installation - enter image description here

j'ai écrit l'exemple pour répondre à une autre question, le code et son explication est mentionnée ici .

11
répondu Kapoor 2017-05-23 12:02:47

j'ai trouvé plus facile dans les liens suivants:

In http://www.remondo.net/visitor-pattern-example-csharp / j'ai trouvé un exemple qui montre un exemple simulé qui montre ce qui est l'avantage du modèle de visiteur. Ici, vous avez différentes classes de conteneurs pour Pill :

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

comme vous le voyez ci-dessus, vous BilsterPack contiennent des paires de pilules' donc vous devez multiplier le nombre de paires par 2. Aussi, vous remarquerez peut-être que Bottle utilise unit qui est un type de données différent et doit être moulé.

ainsi, dans la méthode principale, vous pouvez calculer le nombre de pilules en utilisant le code suivant:

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

notez que le code ci-dessus viole Single Responsibility Principle . Cela signifie que vous devez changer le code de la méthode principale si vous ajoutez un nouveau type de conteneur. Aussi faire interrupteur plus long est une mauvaise pratique.

ainsi en introduisant le code suivant:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

vous avez transféré la responsabilité du nombre de comptage de Pill s à la classe appelée PillCountVisitor (et nous avons supprimé l'énoncé de cas de commutation). Ce moyen est chaque fois que vous avez besoin d'ajouter un nouveau type de récipient de pilule, vous devez changer seulement PillCountVisitor classe. Notez également IVisitor interface est général pour l'utilisation dans d'autres scénarios.

par l'ajout de la méthode Accept à la classe du récipient à comprimés:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

nous permettons aux visiteurs de visiter le conteneur de pilules classe.

à la fin nous calculons le nombre de pilules en utilisant le code suivant:

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

c'est-à-dire que chaque contenant de pilules permet au visiteur PillCountVisitor de voir leurs pilules Compter. Il sait compter tes pilules.

au visitor.Count a la valeur de pilules.

In http://butunclebob.com/ArticleS.UncleBob.IuseVisitor vous voyez le scénario réel dans lequel vous ne pouvez pas utilisez polymorphisme (la réponse) pour suivre le principe de la Responsabilité Unique. En fait dans:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

la méthode reportQtdHoursAndPay est utilisée pour la déclaration et la représentation, ce qui viole le principe de Responsabilité Unique. Il est donc préférable d'utiliser des habitudes des visiteurs pour surmonter le problème.

8
répondu Seyed Morteza Mousavi 2017-05-23 12:18:23

à mon avis, la quantité de travail pour ajouter une nouvelle opération est plus ou moins la même en utilisant Visitor Pattern ou la modification directe de chaque structure d'élément. En outre, si je devais ajouter une nouvelle classe d'éléments , par exemple Cow , l'interface de fonctionnement sera affectée et cela se propage à toutes les classes d'éléments existantes, ce qui nécessite une recompilation de toutes les classes d'éléments. Alors quel est le point?

5
répondu kaosad 2013-03-09 06:41:40

Visiteur Motif que le même souterrain de la mise en œuvre de l'Aspect programmation de l'Objet..

par exemple si vous définissez une nouvelle opération sans changer les classes des éléments sur lesquels elle opère

4
répondu mixturez 2013-06-25 22:07:33

Cay Horstmann a un excellent exemple de l'endroit où appliquer visiteur dans son oo Design and patterns book . Il résume le problème:

les objets composés ont souvent une structure complexe, composée d'éléments individuels. Certains éléments peuvent avoir des éléments d'enfant. ... Une opération sur un élément visite ses éléments enfants, leur applique l'opération et combine les résultats. ... Cependant, il n'est pas facile d'ajouter de nouveaux les opérations d'une telle conception.

la raison pour laquelle ce n'est pas facile est que les opérations sont ajoutées dans les classes de structure elles-mêmes. Par exemple, imaginez que vous avez un système de fichiers:

FileSystem class diagram

voici quelques opérations (fonctionnalités) que nous pourrions vouloir mettre en œuvre avec cette structure:

  • afficher les noms des éléments de noeud (un listing de fichier)
  • afficher la taille calculée des éléments de noeud (où la taille d'un répertoire inclut la taille de tous ses éléments enfant)
  • etc.

vous pouvez ajouter des fonctions à chaque classe dans le système de fichiers pour implémenter les opérations (et les gens ont fait cela dans le passé car il est très évident comment le faire). Le problème est que chaque fois que vous ajoutez une nouvelle fonctionnalité (le "etc."ligne ci-dessus) , vous pourriez avoir besoin d'ajouter plus et plus de méthodes pour les classes de structure. À un certain point, après un certain nombre d'opérations que vous avez ajoutées à votre logiciel, les méthodes de ces classes n'ont plus de sens en termes de cohésion fonctionnelle des classes. Par exemple, vous avez un FileNode qui a une méthode calculateFileColorForFunctionABC() afin de mettre en œuvre la dernière fonctionnalité de visualisation sur le système de fichiers.

le motif du visiteur (comme de nombreux motifs) est né de la douleur et souffrance de développeurs qui savaient qu'il y avait une meilleure façon de permettre à leur code de changer sans exiger beaucoup de changements partout et aussi en respectant de bons principes de conception (haute cohésion, faible couplage). C'est mon opinion qu'il est difficile de comprendre l'utilité de beaucoup de modèles jusqu'à ce que vous avez ressenti cette douleur. Expliquer la douleur (comme nous essayons de faire ci-dessus avec le "etc."les fonctionnalités qui sont ajoutées) occupe de l'espace dans l'explication et est une distraction. Comprendre les modèles est dur pour cette raison.

Visitor nous permet de découpler les fonctionnalités sur la structure de données (par exemple, FileSystemNodes ) des structures de données elles-mêmes. Le modèle permet au design de respecter la cohésion -- les classes de structure de données sont plus simples (elles ont moins de méthodes) et les fonctionnalités sont aussi encapsulées dans des implémentations Visitor . Cela se fait via expédition double (qui est la partie compliquée du modèle): en utilisant accept() méthodes dans les classes de structure et visitX() méthodes dans les classes de visiteur (la fonctionnalité):

FileSystem class diagram with Visitor applied

cette structure nous permet d'ajouter de nouvelles fonctionnalités qui fonctionnent sur la structure en tant que visiteurs de béton (sans changer les classes de structure).

FileSystem class diagram with Visitor applied

par exemple, un PrintNameVisitor qui implémente le répertoire une fonctionnalité, et un PrintSizeVisitor qui implémente la version avec la taille. On pourrait imaginer un jour avoir un "ExportXMLVisitor" qui génère les données en XML, ou un autre visiteur qui les génère en JSON, etc. Nous pourrions même avoir un visiteur qui affiche mon arborescence de répertoires en utilisant un langage graphique tel que le point , pour être visualisé avec un autre programme.

comme une note finale: la complexité du visiteur avec sa double expédition signifie qu'il est plus difficile comprendre, coder et déboguer. En bref, il a un facteur de geek élevé et va à nouveau le principe de baiser. dans un sondage effectué par des chercheurs, le visiteur s'est révélé être un modèle controversé (il n'y avait pas de consensus sur son utilité). Certaines expériences ont même montré que cela ne rendait pas le code plus facile à maintenir.

4
répondu Fuhrmanator 2017-02-08 15:02:44

Description rapide du profil des visiteurs. les classes qui doivent être modifiées doivent toutes appliquer la méthode "accept". Les Clients appellent cette méthode d'acceptation pour effectuer de nouvelles actions sur cette famille de classes, étendant ainsi leur fonctionnalité. Les Clients sont en mesure d'utiliser cet accepter méthode pour effectuer un large éventail de nouvelles actions en passant dans un autre visiteur classe pour chaque action spécifique. Une classe de visiteurs contient plusieurs méthodes de visite outrepassées définir comment réaliser cette même action spécifique pour chaque classe au sein de la famille. Ces visite méthodes adopté une instance de travail.

quand vous pourriez envisager de l'utiliser

  1. quand vous avez une famille de classes vous savez que vous allez devoir ajouter beaucoup de nouvelles actions eux tous, mais pour une raison quelconque, vous n'êtes pas en mesure de modifier ou de recompiler la famille de classes dans le futur.
  2. quand vous voulez ajouter une nouvelle action et avoir cette nouvelle action entièrement définie au sein d'une classe de visiteur plutôt que répartie entre plusieurs classes.
  3. quand votre patron dit que vous devez produire une gamme de classes qui doit faire quelque chose maintenant !... mais personne ne sait exactement ce que quelque chose est pas encore.
4
répondu andrew pate 2018-02-18 16:16:50

basé sur l'excellente réponse de @Federico A. Ramponi.

imaginez que vous ayez cette hiérarchie:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

que se passe-t-il si vous avez besoin d'ajouter une méthode "Walk" ici? Ce sera douloureux pour toute la conception.

en même temps, l'ajout de la méthode" Walk " génère de nouvelles questions. Et "manger" ou"dormir"? Devons-nous réellement ajouter une nouvelle méthode à la hiérarchie animale pour chaque nouvelle action ou opération que nous voulons? ajouter? C'est laid et le plus important, nous ne pourrons jamais fermer L'interface avec les animaux. Ainsi, avec le motif visiteur, nous pouvons ajouter une nouvelle méthode à la hiérarchie sans modifier la hiérarchie!

donc, il suffit de vérifier et d'exécuter ce C# exemple:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}
3
répondu Tomás Escamez 2016-03-28 23:48:38

visiteur

visiteur permet d'ajouter de nouvelles fonctions virtuelles à une famille de classes sans modifier les classes elles-mêmes; au lieu de cela, on crée une classe visiteur qui implémente toutes les spécialisations appropriées de la fonction virtuelle

Visiteur structure:

enter image description here

utiliser le motif du visiteur si:

  1. des opérations similaires doivent être effectuées sur des objets de différents types groupés dans une structure
  2. vous devez exécuter de nombreuses opérations distinctes et sans rapport. il sépare L'opération de la Structure des objets
  3. de nouvelles opérations doivent être ajoutées sans modification de la structure de l'objet
  4. regrouper les opérations connexes dans une seule classe plutôt que de vous forcer à changer ou à dériver des classes
  5. ajouter des fonctions aux bibliothèques de classe pour lesquelles vous soit ne pas avoir la source ou ne peut pas changer la source

même si visiteur modèle offre la flexibilité d'ajouter une nouvelle opération sans changer le code existant dans L'objet, cette flexibilité s'accompagne d'un inconvénient.

si un nouvel objet Visitable a été ajouté, il nécessite des changements de code dans les classes de visiteurs et de visiteurs . Il existe une solution de rechange pour régler ce problème : utiliser la réflexion, qui aura un impact sur la performance.

"1519190920 extrait de Code":

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

explication:

  1. Visitable ( Element ) est une interface et cette méthode d'interface doit être ajoutée à un ensemble de classes.
  2. Visitor est une interface, qui contient des méthodes pour effectuer une opération sur les éléments Visitable .
  3. GameVisitor est une classe qui implémente Visitor interface ( ConcreteVisitor ).
  4. chaque élément Visitable accepte Visitor et invoque une méthode pertinente de Visitor interface.
  5. vous pouvez traiter Game comme Element et des jeux en béton comme Chess,Checkers and Ludo comme ConcreteElements .

dans l'exemple ci-dessus, Chess, Checkers and Ludo sont trois jeux différents ( et Visitable classes). Un beau jour, j'ai rencontré un scénario pour enregistrer les statistiques de chaque jeu. Ainsi, sans modifier la classe individuelle pour mettre en œuvre la fonctionnalité des statistiques, vous pouvez centraliser cette responsabilité dans la classe GameVisitor , ce qui fait l'affaire pour vous sans modifier la la structure de chaque jeu.

sortie:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

se référer à

oodesign article

sourcemaking article

pour plus de détails

Décorateur

Le motif

permet d'ajouter un comportement à un objet individuel, soit statiquement ou dynamiquement, sans affecter le comportement d'autres objets de la même classe

articles connexes:

motif décorateur pour IO

quand utiliser le motif décorateur?

3
répondu Ravindra babu 2017-09-24 03:43:28

Double expédition est juste l'une des raisons parmi d'autres pour utiliser ce modèle .

Mais notez que c'est la seule façon d'implémenter la double ou plus dispatch dans les langues qui utilise un seul paradigme de dispatch.

Voici les raisons d'utiliser le modèle:

1) nous voulons définir de nouvelles opérations sans changer le modèle à chaque fois parce que le modèle ne change pas souvent les opérations filaires changent fréquemment.

2) Nous ne voulons pas de couple modèle et le comportement parce que nous voulons avoir un modèle réutilisable dans plusieurs applications ou nous voulons avoir un modèle extensible qui permettent au client des classes pour définir leurs comportements avec leurs propres classes.

3) nous avons des opérations communes cela dépend du type concret du modèle mais nous ne voulons pas mettre en œuvre la logique dans chaque sous-classe car cela ferait exploser la logique commune dans plusieurs classes et donc dans plusieurs endroits .

4) nous utilisons un modèle de domaine et les classes de modèles de la même hiérarchie exécutent trop de choses distinctes qui pourraient être rassemblées ailleurs .

5) nous avons besoin d'une double expédition .

Nous avons des variables déclarées avec les types d'interface et nous voulons pouvoir les traiter selon leur type d'exécution ... bien sûr sans utiliser if (myObj instanceof Foo) {} ou tout autre truc.

L'idée est par exemple de passer ces variables à des méthodes qui déclarent un type concret de l'interface comme paramètre pour appliquer un traitement spécifique. Cette façon de faire n'est pas possible de sortir de la box with languages repose sur une seule expédition car le choix invoqué à l'exécution dépend uniquement du type d'exécution du récepteur.

Notez Qu'en Java, la méthode (signature) à appeler est choisie au moment de la compilation et dépend du type déclaré des paramètres, pas de leur type d'exécution.

le dernier point qui est une raison d'utiliser le visiteur est aussi une conséquence parce que vous mettez en œuvre le visiteur (bien sûr pour les langues qui ne prennent pas en charge l'envoi multiple), vous devez nécessairement introduire une implémentation de l'envoi double.

Notez que la traversée des éléments (itération) pour appliquer le visiteur sur chacun n'est pas une raison pour utiliser le modèle.

Vous utilisez le modèle parce que vous divisez le modèle et le traitement.

Et en utilisant le modèle, vous bénéficiez en outre d'un itérateur capacité.

Cette capacité est très puissante et va au-delà de l'itération sur type commun avec une méthode spécifique comme accept() est une méthode générique.

C'est un cas d'utilisation. Donc, je vais mettre ça de côté.


exemple en Java

, je vais illustrer la valeur ajoutée du modèle avec un jeu d'échecs où nous voudrions définir traitement joueur demande une pièce en mouvement.

sans l'utilisation de motif de visiteur, nous pourrions définir des comportements de déplacement de pièce directement dans les sous-classes de pièces.

Nous pourrions par exemple avoir une interface Piece telle que:

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

chaque sous-classe de pièce le mettrait en œuvre tel que :

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

et le même chose pour toutes les sous-classes de pièce.

Voici une classe de diagramme qui illustre cette conception:

[model class diagram

cette approche présente trois inconvénients importants:

- les comportements tels que performMove() ou computeIfKingCheck() très probablement utiliser la logique commune.

Pour exemple quel que soit le Piece , performMove() positionnera finalement la pièce actuelle à un endroit précis et prendra potentiellement la pièce adverse.

Diviser les comportements liés dans plusieurs classes au lieu de les rassembler va à l'encontre du modèle de responsabilité unique. Rendant leur maintenabilité plus difficile.

- le traitement en tant que checkMoveValidity() ne doit pas être quelque chose que les sous-classes Piece peuvent voir ou modifier.

C'est un contrôle qui va au-delà des actions humaines ou informatiques. Cette vérification est effectuée à chaque action demandée par un joueur pour s'assurer que la pièce demandée est valide.

Donc nous ne voulons même pas fournir cela dans l'interface Piece .

- dans les jeux d'Échecs difficile pour les développeurs bot, généralement l'application fournit une API standard ( Piece interfaces, sous-classes, planche, comportements communs, etc...) et laissez les développeurs enrichir leur stratégie bot.

Pour pouvoir faire cela, nous devons proposer un modèle où les données et les comportements ne sont pas étroitement couplés dans les implémentations Piece .

alors allons utiliser le motif visiteur !

nous avons deux types de structure:

- les classes modèles qui acceptent d'être visitées (les pièces)

- le visiteurs qui les visitent (déménagements)

voici un diagramme de classe qui illustre le modèle :

enter image description here

Dans la partie supérieure, nous avons les visiteurs et dans la partie inférieure, nous avons les classes du modèle.

Voici l'interface PieceMovingVisitor (comportement spécifié pour chaque type de Piece ):

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

la pièce est définie maintenant:

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

sa méthode clé est:

void accept(PieceMovingVisitor pieceVisitor);

il fournit la première expédition : une invocation basée sur le récepteur Piece .

Au moment de la compilation, la méthode est liée à la méthode accept() de L'interface Piece et à l'exécution, la méthode limitée sera invoquée dans la classe Piece .

Et c'est l'implémentation de la méthode accept() qui effectuera un second envoi.

en effet, chaque sous-classe Piece qui veut être visitée par un objet PieceMovingVisitor invoque la méthode PieceMovingVisitor.visit() en passant comme argument lui-même.

De cette façon, le compilateur limite dès le moment de la compilation, le type du paramètre déclaré avec le type concret.

Il est le second de l'expédition.

Voici la sous-classe Bishop qui illustre que:

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

et voici un exemple d'usage:

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

inconvénients pour les visiteurs

le motif visiteur est un motif très puissant, mais il a aussi quelques limites importantes que vous devriez considérer avant de l'utiliser.

1) risque de réduire / briser l'encapsulation

dans certains types d'opérations, le motif du visiteur peut réduire ou briser l'encapsulation des objets du domaine.

par exemple, comme la classe MovePerformingVisitor doit définir les coordonnées de la pièce réelle, l'interface Piece doit fournir un moyen de le faire:

void setCoordinates(Coordinates coordinates);

la responsabilité des changements de coordonnées Piece est maintenant ouverte à d'autres classes que les sous-classes Piece .

Le déplacement du traitement effectué par le visiteur dans les sous-classes Piece n'est pas non plus une option.

Il va en effet créer un autre problème que le Piece.accept() accepte toute mise en œuvre visiteur. Il ne sait pas ce que le visiteur exécute et donc aucune idée si et comment changer l'état de la pièce.

Une façon d'identifier le visiteur serait d'effectuer un traitement de poste dans Piece.accept() selon l'implémentation du visiteur. Ce serait une très mauvaise idée car il créerait un couplage élevé entre les implémentations de visiteur et les sous-classes de pièce et en outre il faudrait probablement utiliser trick comme getClass() , instanceof ou n'importe quel marqueur identifiant l'implémentation de visiteur.

2) exigence de changement de modèle

contrairement à d'autres modèles de comportement comme Decorator par exemple, le modèle de visiteur est intrusif.

Nous avons en effet besoin de modifier la classe de récepteur initiale pour fournir une méthode accept() pour accepter d'être visité.

Nous n'avons pas eu de problème pour Piece et ses sous-classes car celles-ci sont nos classes .

Dans intégré ou tiers les cours, les choses ne sont pas si faciles.

Nous devons les envelopper ou hériter (si nous le pouvons) pour ajouter la méthode accept() .

3) indirectes

le motif crée des multiples indirects.

La double expédition signifie deux invocations au lieu d'une seule:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

et nous pourrions avoir des le visiteur change l'état de l'objet visité.

Il peut ressembler à un cycle :

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)
3
répondu davidxxx 2017-12-25 23:21:42

Alors que j'ai compris le comment et quand, je n'ai jamais compris le pourquoi. Dans le cas où il aide n'importe qui avec un fond dans un langage comme C++, vous voulez lire ce très soigneusement.

pour les paresseux, nous utilisons le motif visiteur parce que " tandis que les fonctions virtuelles sont envoyées dynamiquement en C++, la surcharge de la fonction se fait statiquement " .

ou, autrement dit, pour s'assurer que CollideWith (ApolloSpacecraft&) est appelé lorsque vous passez dans une référence de vaisseau spatial qui est en fait lié à un objet ApolloSpacecraft.

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}
1
répondu Carl 2013-11-25 23:04:31

j'aime vraiment la description et l'exemple de http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .

l'hypothèse est que vous avez une hiérarchie de classe primaire qui est fixe; peut-être qu'elle vient d'un autre vendeur et vous ne pouvez pas faire des changements à cette hiérarchie. Cependant, votre intention est que vous souhaitez ajouter de nouvelles méthodes polymorphes de la hiérarchie, ce qui signifie que normalement, vous auriez à ajouter quelque chose à l'interface de la classe de base. Donc le dilemme est que vous devez ajouter des méthodes à la classe de base, mais vous ne pouvez pas toucher la classe de base. Comment contourner ce problème?

le modèle de conception qui résout ce genre de problème est appelé un" visiteur " (le dernier dans le Design Patterns book), et il construit sur le schéma de répartition double montré dans la dernière section.

Le modèle visiteur vous permet d'étendre l'interface du type primaire en créant une hiérarchie de classe séparée de visiteur de type pour virtualiser les opérations effectuées sur le type primaire. Les objets du type primaire "acceptent" simplement le visiteur, puis appellent la fonction membre liée dynamiquement du visiteur.

1
répondu wojcikstefan 2016-11-04 15:35:30

lorsque vous voulez avoir des objets de fonction sur les types de données de l'union, vous aurez besoin de visitor pattern.

vous pourriez vous demander ce que les objets de fonction et les types de données de l'union sont, alors il est intéressant de lire http://www.ccs.neu.edu/home/matthias/htdc.html

0
répondu Wei Qiu 2017-06-14 14:40:29

Merci pour l'explication géniale de @Federico A. Ramponi , je viens de le faire dans java version. Espérons qu'il pourrait être utile.

aussi exactement comme @Konrad Rudolph a souligné, c'est en fait un Double expédition en utilisant deux instances concrètes ensemble pour déterminer les méthodes d'exécution.

donc en fait, il n'est pas nécessaire de créer une interface commune pour opération exécuteur tant que nous avons l'interface opération correctement défini.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

comme vous vous en doutez, une interface commune nous apportera plus de clarté même si ce n'est en fait pas la partie essentielle dans ce modèle.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}
0
répondu Hearen 2018-06-09 03:40:59

votre question est quand savoir:

Je ne code pas d'abord avec le motif du visiteur. je code standard, et attendez que la nécessité de se produire et ensuite de refactor. donc, disons que vous avez de multiples systèmes de paiement que vous avez installé un à la fois. Au moment du départ, vous pouvez avoir beaucoup de conditions if (ou instanceOf), par exemple:

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

imaginez que j'avais 10 méthodes de paiement, ça devient un peu moche. Quand vous voyez ce genre de modèle se produisant les visiteurs viennent à la main pour séparer tout cela et vous finissez par appeler quelque chose comme ça après:

new PaymentCheckoutVistor(paymentType).visit()

vous pouvez voir comment l'implémenter à partir du nombre d'exemples ici, je vous montre juste une usecase.

0
répondu j2emanue 2018-06-25 03:15:35