Je continue de voir des références au profil des visiteurs dans les blogs mais je dois admettre que je ne comprends pas. J'ai lu l' article wikipedia pour le motif et je comprends sa mécanique, mais je ne sais toujours pas quand je l'utiliserai.
En tant que quelqu'un qui a récemment obtenu le motif de décoration et qui en voit maintenant les utilisations absolument partout, j'aimerais pouvoir comprendre vraiment intuitivement ce motif apparemment pratique.
design-patterns
visitor-pattern
George Mauer
la source
la source
Réponses:
Je ne connais pas très bien le modèle des visiteurs. Voyons si j'ai bien compris. Supposons que vous ayez une hiérarchie d'animaux
(Supposons que ce soit une hiérarchie complexe avec une interface bien établie.)
Maintenant, nous voulons ajouter une nouvelle opération à la hiérarchie, à savoir que nous voulons que chaque animal émette son son. Dans la mesure où la hiérarchie est aussi simple, vous pouvez le faire avec un polymorphisme simple:
Mais en procédant ainsi, chaque fois que vous souhaitez ajouter une opération, vous devez modifier l'interface pour chaque classe unique de la hiérarchie. Supposons maintenant que vous soyez satisfait de l'interface d'origine et que vous souhaitiez y apporter le moins de modifications possibles.
Le modèle de visiteur vous permet de déplacer chaque nouvelle opération dans une classe appropriée, et vous devez étendre l'interface de la hiérarchie une seule fois. Faisons le. Tout d'abord, nous définissons une opération abstraite (la classe "Visitor" dans GoF ) qui a une méthode pour chaque classe de la hiérarchie:
Ensuite, nous modifions la hiérarchie afin d'accepter de nouvelles opérations:
Enfin, nous implémentons l'opération réelle, sans modifier ni Chat ni Chien :
Vous avez maintenant un moyen d'ajouter des opérations sans modifier la hiérarchie. Voici comment cela fonctionne:
la source
letsDo(Operation *v)
a besoin d'un pointeur.theSound.hereIsACat(c)
aurait fait le travail, comment justifiez-vous tous les frais généraux introduits par le modèle? la double répartition est la justification.La raison de votre confusion est probablement que le visiteur est un terme impropre fatal. De nombreux programmeurs ( 1 éminent !) Sont tombés sur ce problème. Ce qu'il fait, c'est implémenter la double répartition dans des langues qui ne le supportent pas nativement (la plupart d'entre elles ne le font pas).
1) Mon exemple préféré est Scott Meyers, auteur acclamé de "Effective C ++", qui a appelé celui-ci l'un de ses plus importants C ++ aha! moments jamais .
la source
switch
:switch
disques codes de la prise de décision sur le côté client (duplication de code) et ne propose pas de vérification de type statique ( vérifier l'exhaustivité et la distinction des cas, etc.). Un modèle de visiteur est vérifié par le vérificateur de type et rend généralement le code client plus simple.virtual
fonctionnalités similaires sont si utiles dans les langages de programmation modernes - elles sont le bloc de construction de base des programmes extensibles - à mon avis, le chemin c (commutateur imbriqué ou correspondance de modèle, etc. selon la langue de votre choix) est beaucoup plus propre dans le code qui n'a pas besoin d'être extensible et j'ai été agréablement surpris de voir ce style dans un logiciel compliqué comme le prouveur 9. Plus important encore, tout langage qui veut fournir une extensibilité devrait probablement s'adapter à de meilleurs schémas de distribution que la distribution récursive simple (c'est-à-dire visiteur).Tout le monde ici a raison, mais je pense que cela ne tient pas compte du "quand". Tout d'abord, à partir des modèles de conception:
Maintenant, pensons à une hiérarchie de classes simple. J'ai les classes 1, 2, 3 et 4 et les méthodes A, B, C et D. Disposez-les comme dans une feuille de calcul: les classes sont des lignes et les méthodes sont des colonnes.
Maintenant, la conception orientée 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 simplement une nouvelle classe, spécifiez ce qui est différent dans cette classe et héritez du reste.
Parfois, cependant, les classes sont relativement statiques, mais vous devez ajouter plus de méthodes fréquemment - en ajoutant des colonnes. La manière standard dans une conception OO serait d'ajouter de telles méthodes à toutes les classes, ce qui peut être coûteux. Le modèle Visitor rend cela facile.
À propos, c'est le problème que les correspondances de motifs de Scala ont l'intention de résoudre.
la source
Le modèle de conception de visiteur fonctionne très bien pour les structures "récursives" comme les arborescences de répertoires, les structures XML ou les contours de documents.
Un objet Visitor visite chaque nœud de la structure récursive: chaque répertoire, chaque balise XML, peu importe. L'objet Visitor ne boucle pas à travers la structure. Au lieu de cela, les méthodes Visitor sont appliquées à chaque nœud de la structure.
Voici une structure de nœud récursive typique. Il peut s'agir d'un répertoire ou d'une balise XML. [Si vous êtes une personne Java, imaginez de nombreuses méthodes supplémentaires pour créer et gérer la liste des enfants.]
La
visit
méthode applique un objet Visitor à chaque nœud de la structure. Dans ce cas, c'est un visiteur descendant. Vous pouvez modifier la structure de lavisit
méthode pour effectuer un ordre ascendant ou un autre ordre.Voici une superclasse pour les visiteurs. Il est utilisé par la
visit
méthode. Il "arrive" à chaque nœud de la structure. Puisque lavisit
méthode appelleup
etdown
, le visiteur peut suivre la profondeur.Une sous-classe pourrait faire des choses comme compter les nœuds à chaque niveau et accumuler une liste de nœuds, générant un joli numéro de section hiérarchique de chemin.
Voici une application. Il construit une structure arborescente,
someTree
. Il crée unVisitor
,dumpNodes
.Ensuite, il applique le
dumpNodes
à l'arbre. L'dumpNode
objet "visitera" chaque nœud de l'arborescence.L'
visit
algorithme TreeNode garantit que chaque TreeNode est utilisé comme argument de laarrivedAt
méthode du visiteur .la source
Une façon de voir les choses est que le modèle de visiteur est un moyen de laisser vos clients ajouter des méthodes supplémentaires à toutes vos classes dans une hiérarchie de classes particulière.
Il est utile lorsque vous avez une hiérarchie de classes assez stable, mais que vous avez des exigences changeantes quant à ce qui doit être fait avec cette hiérarchie.
L'exemple classique concerne les compilateurs et similaires. Un arbre de syntaxe abstraite (AST) peut définir avec précision la structure du langage de programmation, mais les opérations que vous voudrez peut-être effectuer sur l'AST changeront au fur et à mesure de l'avancement de votre projet: générateurs de code, jolies imprimantes, débogueurs, analyse des métriques de complexité.
Sans le modèle de visiteur, chaque fois qu'un développeur souhaite ajouter une nouvelle fonctionnalité, il doit 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 distincte ou sont produites par une équipe distincte.
(J'ai entendu dire que le modèle de visiteur est en conflit avec les bonnes pratiques d'OO, car il éloigne les opérations des données des données. Le modèle de visiteur est utile précisément dans la situation où les pratiques normales d'OO échouent.)
la source
Il y a au moins trois très bonnes raisons d'utiliser le modèle de visiteur:
Réduisez la prolifération de code qui n'est que légèrement différent lorsque les structures de données changent.
Appliquer le même calcul à plusieurs structures de données, sans changer le code qui implémente le calcul.
Ajoutez des informations aux bibliothèques héritées sans modifier le code hérité.
Veuillez consulter un article que j'ai écrit à ce sujet .
la source
Comme Konrad Rudolph l'a déjà souligné, il convient aux cas où nous avons besoin d'une double expédition
Voici un exemple pour montrer une situation où nous avons besoin d'une double répartition et comment le visiteur nous aide à le faire.
Exemple :
Disons que j'ai 3 types d'appareils mobiles - iPhone, Android, Windows Mobile.
Ces trois appareils sont équipés d'une radio Bluetooth.
Supposons que la radio Blue tooth peut provenir de 2 OEM distincts - Intel et Broadcom.
Juste pour rendre l'exemple pertinent pour notre discussion, supposons également que les API exposées par la radio Intel sont différentes de celles exposées par la radio Broadcom.
Voici à quoi ressemblent mes cours -
Maintenant, je voudrais introduire une opération - Allumer le Bluetooth sur un appareil mobile.
Sa signature de fonction devrait aimer quelque chose comme ça -
Ainsi, selon le bon type d'appareil et selon le bon type de radio Bluetooth , il peut être allumé en appelant les étapes ou l'algorithme appropriés .
En principe, il devient une matrice 3 x 2, où j'essaie de vectoriser la bonne opération en fonction du bon type d'objets impliqués.
Un comportement polymorphe en fonction du type des deux arguments.
Maintenant, le modèle de visiteur peut être appliqué à ce problème. L'inspiration vient de la page Wikipédia déclarant - «En substance, le visiteur permet d'ajouter de nouvelles fonctions virtuelles à une famille de classes sans modifier les classes elles-mêmes; à la place, on crée une classe visiteur qui implémente toutes les spécialisations appropriées de la fonction virtuelle. Le visiteur prend la référence d'instance comme entrée et met en œuvre l'objectif via une double répartition. »
La double répartition est une nécessité ici en raison de la matrice 3x2
Voici à quoi ressemblera la configuration -
J'ai écrit l'exemple pour répondre à une autre question, le code et son explication sont mentionnés ici .
la source
Je l'ai trouvé plus facile dans les liens suivants:
Dans http://www.remondo.net/visitor-pattern-example-csharp/, j'ai trouvé un exemple qui montre un exemple simulé qui montre les avantages du modèle de visiteur. Ici, vous avez différentes classes de conteneurs pour
Pill
:Comme vous le voyez ci-dessus, vous
BilsterPack
contenez des paires de pilules, vous devez donc multiplier le nombre de paires par 2. Vous pouvez également remarquer cetteBottle
utilisationunit
qui est d'un type de données différent et doit être castée.Donc, dans la méthode principale, vous pouvez calculer le nombre de comprimés en utilisant le code suivant:
Notez que le code ci-dessus viole
Single Responsibility Principle
. Cela signifie que vous devez modifier le code de la méthode principale si vous ajoutez un nouveau type de conteneur. Prolonger également le passage est une mauvaise pratique.Donc en introduisant le code suivant:
Vous avez transféré la responsabilité de compter le nombre de
Pill
s à la classe appeléePillCountVisitor
(et nous avons supprimé l'instruction switch case). Cela signifie que chaque fois que vous devez ajouter un nouveau type de pilulier, vous ne devez modifier que laPillCountVisitor
classe. Notez également que l'IVisitor
interface est générale à utiliser dans d'autres scénarios.En ajoutant la méthode Accept à la classe de conteneur de pilules:
nous permettons au visiteur de visiter les classes de conteneurs de pilules.
À la fin, nous calculons le nombre de comprimés à l'aide du code suivant:
Cela signifie que: chaque boîte à pilules permet au
PillCountVisitor
visiteur de voir le nombre de ses comprimés. Il sait compter vos comprimés.A la
visitor.Count
valeur des pilules.Dans http://butunclebob.com/ArticleS.UncleBob.IuseVisitor, vous voyez un scénario réel dans lequel vous ne pouvez pas utiliser le polymorphisme (la réponse) pour suivre le principe de responsabilité unique. En fait dans:
la
reportQtdHoursAndPay
méthode est pour le signalement et la représentation et cela viole le principe de responsabilité unique. Il est donc préférable d'utiliser le modèle de visiteur pour surmonter le problème.la source
La double expédition n'est qu'une raison parmi d'autres d'utiliser ce modèle .
Mais notez que c'est la seule façon d'implémenter une répartition double ou plus dans les langues qui utilise un paradigme de répartition unique.
Voici les raisons d'utiliser le modèle:
1) Nous voulons définir de nouvelles opérations sans changer le modèle à chaque fois car le modèle ne change pas souvent alors que les opérations changent fréquemment.
2) Nous ne voulons pas coupler modèle et comportement car nous voulons avoir un modèle réutilisable dans plusieurs applications ou nous voulons avoir un modèle extensible qui permet aux classes clientes de définir leurs comportements avec leurs propres classes.
3) Nous avons des opérations communes qui dépendent du type concret du modèle mais nous ne voulons pas implémenter la logique dans chaque sous-classe car cela exploserait la logique commune dans plusieurs classes et donc à plusieurs endroits .
4) Nous utilisons une conception de modèle de domaine et les classes de modèle de la même hiérarchie effectuent 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 des types d'interface et nous voulons pouvoir les traiter en fonction de leur type d'exécution… bien sûr sans utiliser
if (myObj instanceof Foo) {}
ni 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 à la sortie de la boîte avec les langues repose sur une seule expédition car le choix invoqué au moment de 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 également une conséquence car lorsque vous implémentez le visiteur (bien sûr pour les langues qui ne prennent pas en charge la répartition multiple), vous devez nécessairement introduire une implémentation à double répartition.
Notez que la traversée d'éléments (itération) pour appliquer le visiteur sur chacun d'eux n'est pas une raison pour utiliser le motif.
Vous utilisez le modèle car vous divisez le modèle et le traitement.
Et en utilisant le modèle, vous bénéficiez en plus d'une capacité d'itérateur.
Cette capacité est très puissante et va au-delà de l'itération sur un type commun avec une méthode spécifique comme
accept()
c'est une méthode générique.Il s'agit d'un cas d'utilisation spécial. Je vais donc mettre cela de côté.
Exemple en Java
Je vais illustrer la valeur ajoutée du motif avec un exemple d'échecs où nous aimerions définir le traitement lorsque le joueur demande le déplacement d'une pièce.
Sans l'utilisation du modèle de visiteur, nous pourrions définir des comportements de déplacement de pièces directement dans les sous-classes de pièces.
On pourrait avoir par exemple une
Piece
interface telle que:Chaque sous-classe Piece le mettrait en œuvre, par exemple:
Et la même chose pour toutes les sous-classes Piece.
Voici une classe de diagramme qui illustre cette conception:
Cette approche présente trois inconvénients importants:
- des comportements tels que
performMove()
oucomputeIfKingCheck()
utiliseront très probablement une logique commune.Par exemple, quel que soit le béton
Piece
,performMove()
définira finalement la pièce actuelle à un emplacement spécifique et prendra éventuellement la pièce adverse.Diviser les comportements associés en plusieurs classes au lieu de les rassembler détruit en quelque sorte le modèle de responsabilité unique. Rendre leur maintenabilité plus difficile.
- le traitement
checkMoveValidity()
ne devrait pas être quelque chose que lesPiece
sous-classes peuvent voir ou changer.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 le mouvement de pièce demandé est valide.
Donc, nous ne voulons même pas fournir cela dans l'
Piece
interface.- Dans les jeux d'échecs difficiles pour les développeurs de robots, l'application fournit généralement une API standard (
Piece
interfaces, sous-classes, Board, comportements communs, etc.) et permet aux développeurs d'enrichir leur stratégie de robots.Pour ce faire, nous devons proposer un modèle où les données et les comportements ne sont pas étroitement couplés dans les
Piece
implémentations.Alors allons-y pour utiliser le modèle de visiteur!
Nous avons deux types de structure:
- les classes modèles qui acceptent d'être visitées (les pièces)
- les visiteurs qui les visitent (opérations de déménagement)
Voici un diagramme de classe qui illustre le modèle:
Dans la partie supérieure, nous avons les visiteurs et dans la partie inférieure, nous avons les classes modèles.
Voici l'
PieceMovingVisitor
interface (comportement spécifié pour chaque type dePiece
):La pièce est définie maintenant:
Sa méthode clé est:
Il fournit le premier envoi: une invocation basée sur le
Piece
récepteur.Au moment de la compilation, la méthode est liée à la
accept()
méthode de l'interface Piece et au moment de l'exécution, la méthode limitée sera invoquée sur laPiece
classe d' exécution .Et c'est l'
accept()
implémentation de la méthode qui effectuera une deuxième répartition.En effet, chaque
Piece
sous-classe qui veut être visitée par unPieceMovingVisitor
objet invoque laPieceMovingVisitor.visit()
méthode en passant comme argument lui-même.De cette façon, le compilateur délimite dès la compilation, le type du paramètre déclaré avec le type concret.
Il y a la deuxième dépêche.
Voici la
Bishop
sous-classe qui illustre cela:Et voici un exemple d'utilisation:
Inconvénients pour les visiteurs
Le modèle de visiteur est un modèle très puissant, mais il a également quelques limitations importantes que vous devez considérer avant de l'utiliser.
1) Risque de réduire / casser l'encapsulation
Dans certains types d'opérations, le modèle de visiteur peut réduire ou interrompre l'encapsulation des objets de domaine.
Par exemple, comme la
MovePerformingVisitor
classe doit définir les coordonnées de la pièce réelle, l'Piece
interface doit fournir un moyen de le faire:La responsabilité des
Piece
changements de coordonnées est désormais ouverte à d'autres classes que lesPiece
sous-classes.Déplacer le traitement effectué par le visiteur dans les
Piece
sous-classes n'est pas non plus une option.Cela créera en effet un autre problème car le
Piece.accept()
accepte toute implémentation de visiteur. Il ne sait pas ce que le visiteur effectue et donc aucune idée de savoir si et comment changer l'état de la pièce.Un moyen d'identifier le visiteur serait d'effectuer un post-traitement en
Piece.accept()
fonction de l'implémentation du visiteur. Ce serait une très mauvaise idée car cela créerait un fort couplage entre les implémentations des visiteurs et des sous - classes Piece et d' ailleurs , il aurait probablement besoin d'utiliser comme trucgetClass()
,instanceof
ou tout autre marqueur identifiant la mise en œuvre des visiteurs.2) Obligation de changer de modèle
Contrairement à certains autres modèles de conception comportementale comme
Decorator
par exemple, le modèle de visiteur est intrusif.Il nous faut en effet modifier la classe réceptrice initiale pour fournir une
accept()
méthode à accepter d'être visitée.Nous n'avons eu aucun problème pour
Piece
et ses sous-classes car ce sont nos classes .Dans les classes intégrées ou tierces, les choses ne sont pas si faciles.
Nous devons les envelopper ou les hériter (si nous le pouvons) pour ajouter la
accept()
méthode.3) Indirections
Le motif crée de multiples indirections.
La double répartition signifie deux invocations au lieu d'une seule:
Et nous pourrions avoir des indirections supplémentaires lorsque le visiteur modifie l'état de l'objet visité.
Cela peut ressembler à un cycle:
la source
Cay Horstmann a un excellent exemple où appliquer Visitor dans son livre OO Design and patterns . Il résume le problème:
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:
Voici quelques opérations (fonctionnalités) que nous pourrions vouloir implémenter avec cette structure:
Vous pouvez ajouter des fonctions à chaque classe du FileSystem pour implémenter les opérations (et les gens l'ont fait dans le passé car il est très évident de le faire). Le problème est que chaque fois que vous ajoutez une nouvelle fonctionnalité (la ligne "etc." ci-dessus), vous devrez peut-être ajouter de plus en plus de méthodes aux classes de structure. À un moment donné, 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 disposez d'un
FileNode
qui a une méthodecalculateFileColorForFunctionABC()
afin d'implémenter la dernière fonctionnalité de visualisation sur le système de fichiers.Le modèle de visiteur (comme de nombreux modèles de conception) est né de la douleur et de la souffrance des développeurs qui savaient qu'il y avait une meilleure façon de permettre à leur code de changer sans nécessiter beaucoup de changements partout et en respectant également les bons principes de conception (haute cohésion, faible couplage ). Je pense qu'il est difficile de comprendre l'utilité de nombreux modèles tant que vous n'avez pas ressenti cette douleur. Expliquer la douleur (comme nous essayons de le faire ci-dessus avec les fonctionnalités "etc." qui s'ajoutent) prend de la place dans l'explication et est une distraction. Comprendre les schémas est difficile pour cette raison.
Visitor nous permet de découpler les fonctionnalités de la structure de données (par exemple,
FileSystemNodes
) des structures de données elles-mêmes. Le modèle permet à la conception 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 également encapsulées dans lesVisitor
implémentations. Cela se fait via la double répartition (qui est la partie compliquée du modèle): en utilisant desaccept()
méthodes dans les classes de structure et desvisitX()
méthodes dans les classes Visitor (la fonctionnalité):Cette structure nous permet d'ajouter de nouvelles fonctionnalités qui fonctionnent sur la structure en tant que visiteurs concrets (sans changer les classes de structure).
Par exemple, un
PrintNameVisitor
qui implémente la fonctionnalité de liste de répertoires et unPrintSizeVisitor
qui implémente la version avec la taille. Nous pourrions 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 DOT , à visualiser avec un autre programme.Enfin, la complexité de Visitor avec sa double répartition signifie qu'il est plus difficile à comprendre, à coder et à déboguer. En bref, il a un facteur geek élevé et va contre le principe KISS. Dans une enquête réalisée par des chercheurs, Visitor 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 facilitait pas la maintenance du code.
la source
À mon avis, la quantité de travail pour ajouter une nouvelle opération est plus ou moins la même en utilisant
Visitor Pattern
ou en modifiant directement la structure de chaque élément. De plus, si je devais ajouter une nouvelle classe d'éléments, disonsCow
, l'interface Operation sera affectée et cela se propagera à toutes les classes d'éléments existantes, nécessitant donc une recompilation de toutes les classes d'éléments. Alors à quoi ça sert?la source
rootElement.visit (node) -> node.collapse()
. Avec Visitor, chaque nœud implémente le parcours du graphe pour tous ses enfants, vous avez donc terminé.levelsRemaining
compteur comme paramètre. Décrémentez-le avant d'appeler le prochain niveau d'enfants. À l'intérieur de votre visiteurif(levelsRemaining == 0) return
.Modèle de visiteur comme la même implémentation souterraine pour la programmation Aspect Object.
Par exemple, si vous définissez une nouvelle opération sans changer les classes des éléments sur lesquels elle opère
la source
Description rapide du modèle de visiteur. Les classes qui nécessitent une modification doivent toutes implémenter la méthode «accept». Les clients appellent cette méthode d'acceptation pour effectuer une nouvelle action sur cette famille de classes, étendant ainsi leurs fonctionnalités. Les clients peuvent utiliser cette méthode d'acceptation unique pour effectuer un large éventail de nouvelles actions en transmettant une classe de visiteurs différente pour chaque action spécifique. Une classe de visiteurs contient plusieurs méthodes de visite redéfinies définissant comment réaliser la même action spécifique pour chaque classe de la famille. Ces méthodes de visite reçoivent une instance sur laquelle travailler.
Quand pourriez-vous envisager de l'utiliser
la source
Je n'ai pas compris ce schéma jusqu'à ce que je tombe sur un article d' oncle bob et que je lise des commentaires. Considérez le code suivant:
Bien qu'il puisse sembler bon car il confirme la responsabilité unique, il viole le principe ouvert / fermé . Chaque fois que vous avez un nouveau type d'employé, vous devrez l'ajouter avec la vérification de type. Et si vous ne le faites pas, vous ne le saurez jamais au moment de la compilation.
Avec le modèle de visiteur, vous pouvez rendre votre code plus propre car il ne viole pas le principe ouvert / fermé et ne viole pas la responsabilité unique. Et si vous oubliez d'implémenter visit, il ne sera pas compilé:
La magie est que
v.Visit(this)
même si elle est identique, elle est en fait différente, car elle appelle différentes surcharges de visiteurs.la source
Basé sur l'excellente réponse de @Federico A. Ramponi.
Imaginez simplement que vous avez cette hiérarchie:
Que se passe-t-il si vous devez ajouter une méthode "Walk" ici? Ce sera pénible pour l'ensemble du design.
Dans le même temps, l'ajout de la méthode "Walk" génère de nouvelles questions. Qu'en est-il de «manger» ou de «dormir»? Faut-il vraiment ajouter une nouvelle méthode à la hiérarchie animale pour chaque nouvelle action ou opération que nous voulons ajouter? C'est moche et le plus important, nous ne pourrons jamais fermer l'interface Animal. Ainsi, avec le modèle de visiteur, nous pouvons ajouter une nouvelle méthode à la hiérarchie sans modifier la hiérarchie!
Donc, vérifiez et exécutez cet exemple C #:
la source
Dog
aussi bien queCat
. Vous auriez pu les faire dans la classe de base afin qu'ils soient hérités ou choisissez un exemple approprié.Visiteur
Structure des visiteurs:
Utilisez le modèle Visiteur si:
Même si le modèle de visiteur offre la flexibilité d'ajouter de nouvelles opérations sans modifier le code existant dans Object, cette flexibilité présente un inconvénient.
Si un nouvel objet Visitable a été ajouté, il nécessite des modifications de code dans les classes Visitor & ConcreteVisitor . Il existe une solution de contournement pour résoudre ce problème: utilisez la réflexion, qui aura un impact sur les performances.
Extrait de code:
Explication:
Visitable
(Element
) est une interface et cette méthode d'interface doit être ajoutée à un ensemble de classes.Visitor
est une interface, qui contient des méthodes pour effectuer une opération sur desVisitable
éléments.GameVisitor
est une classe qui implémenteVisitor
interface (ConcreteVisitor
).Visitable
élément accepteVisitor
et appelle une méthode d'Visitor
interface pertinente .Game
asElement
et concrets commeChess,Checkers and Ludo
asConcreteElements
.Dans l'exemple ci-dessus, il
Chess, Checkers and Ludo
y a trois jeux (etVisitable
classes) différents. Un beau jour, j'ai rencontré un scénario pour enregistrer les statistiques de chaque match. Ainsi, sans modifier une classe individuelle pour implémenter la fonctionnalité de statistiques, vous pouvez centraliser cette responsabilité enGameVisitor
classe, ce qui fait l'affaire pour vous sans modifier la structure de chaque jeu.production:
Faire référence à
article oodesign
article de sourcemaking
pour plus de détails
Décorateur
Articles Similaires:
Modèle de décorateur pour IO
Quand utiliser le motif décorateur?
la source
J'aime vraiment la description et l'exemple de http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .
la source
Bien que j'aie compris le comment et le quand, je n'ai jamais compris le pourquoi. Dans le cas où cela aiderait toute personne ayant une formation dans un langage comme C ++, vous voulez lire cela très attentivement.
Pour les paresseux, nous utilisons le modèle de visiteur parce que "alors que les fonctions virtuelles sont distribuées dynamiquement en C ++, la surcharge des fonctions se fait statiquement" .
Ou, autrement dit, pour vous assurer que CollideWith (ApolloSpacecraft &) est appelé lorsque vous passez une référence SpaceShip qui est en fait liée à un objet ApolloSpacecraft.
la source
Merci pour l'explication impressionnante de @Federico A. Ramponi , je viens de le faire en version java . J'espère que cela pourrait être utile.
Aussi, comme l'a souligné @Konrad Rudolph , il s'agit en fait d'une double répartition utilisant deux instances concrètes ensemble pour déterminer les méthodes d'exécution.
Donc, en réalité, il n'est pas nécessaire de créer une interface commune pour l' exécuteur d' opération tant que l' interface d' opération est correctement définie.
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 de ce modèle.
la source
votre question est de savoir quand:
je ne code pas d'abord avec le modèle de visiteur. je code standard et attend que le besoin se produise, puis refactorise. disons donc que vous avez plusieurs systèmes de paiement que vous avez installés un à la fois. Au moment du paiement, vous pouvez avoir plusieurs conditions if (ou instanceOf), par exemple:
Imaginez maintenant que j'avais 10 méthodes de paiement, ça devient plutôt moche. Donc, quand vous voyez ce type de modèle se produire, le visiteur intervient pour séparer tout cela et vous finissez par appeler quelque chose comme ça après:
Vous pouvez voir comment l'implémenter à partir du nombre d'exemples ici, je vous montre juste un cas d'utilisation.
la source