Modèle de conception de visiteur en Java

1. Vue d'ensemble

Dans ce didacticiel, nous présenterons l'un des modèles de conception comportementaux du GoF: le visiteur.

Tout d'abord, nous expliquerons son but et le problème qu'il tente de résoudre.

Ensuite, nous examinerons le diagramme UML du visiteur et l'implémentation de l'exemple pratique.

2. Modèle de conception des visiteurs

Le but d'un modèle Visiteur est de définir une nouvelle opération sans introduire les modifications dans une structure d'objet existante.

Imaginez que nous avons un compositeobjet qui se compose de composants. La structure de l'objet est fixe - soit nous ne pouvons pas la changer, soit nous ne prévoyons pas d'ajouter de nouveaux types d'éléments à la structure.

Maintenant, comment pourrions-nous ajouter de nouvelles fonctionnalités à notre code sans modification des classes existantes?

Le modèle de conception des visiteurs pourrait être une réponse. En termes simples, nous devrons ajouter une fonction qui accepte la classe de visiteur à chaque élément de la structure.

De cette façon, nos composants permettront à l'implémentation du visiteur de les «visiter» et d'effectuer toute action requise sur cet élément.

En d'autres termes, nous allons extraire l'algorithme qui sera appliqué à la structure d'objet des classes.

Par conséquent, nous ferons bon usage du principe Ouvert / Fermé car nous ne modifierons pas le code, mais nous serons toujours en mesure d'étendre la fonctionnalité en fournissant une nouvelle implémentation de visiteur .

3. Diagramme UML

Sur le diagramme UML ci-dessus, nous avons deux hiérarchies d'implémentation, des visiteurs spécialisés et des éléments concrets.

Tout d'abord, le client utilise une implémentation de Visiteur et l'applique à la structure d'objet. L'objet composite itère sur ses composants et applique le visiteur à chacun d'eux.

Maintenant, ce qui est particulièrement pertinent, c'est que des éléments concrets (ConcreteElementA et ConcreteElementB) acceptent un Visiteur, lui permettant simplement de le visiter .

Enfin, cette méthode est la même pour tous les éléments de la structure, elle effectue un double envoi en se passant (via le mot clé this ) à la méthode de visite du visiteur.

4. Mise en œuvre

Notre exemple sera un objet Document personnalisé qui se compose d'éléments concrets JSON et XML; les éléments ont une superclasse abstraite commune, l' élément.

La classe Document :

public class Document extends Element { List elements = new ArrayList(); // ... @Override public void accept(Visitor v) { for (Element e : this.elements) { e.accept(v); } } }

La classe Element a une méthode abstraite qui accepte l' interface Visiteur :

public abstract void accept(Visitor v);

Par conséquent, lors de la création du nouvel élément, nommez-le JsonElement , nous devrons fournir l'implémentation de cette méthode.

Cependant, en raison de la nature du modèle de visiteur, l'implémentation sera la même, donc dans la plupart des cas, cela nous obligerait à copier-coller le code standard d'un autre élément déjà existant:

public class JsonElement extends Element { // ... public void accept(Visitor v) { v.visit(this); } }

Puisque nos éléments permettent de les visiter par n'importe quel visiteur, disons que nous voulons traiter nos éléments Document , mais chacun d'eux d'une manière différente, en fonction de son type de classe.

Par conséquent, notre visiteur aura une méthode distincte pour le type donné:

public class ElementVisitor implements Visitor { @Override public void visit(XmlElement xe) { System.out.println( "processing an XML element with uuid: " + xe.uuid); } @Override public void visit(JsonElement je) { System.out.println( "processing a JSON element with uuid: " + je.uuid); } }

Ici, notre visiteur concret met en œuvre deux méthodes, une pour chaque type d' élément .

Cela nous donne accès à l'objet particulier de la structure sur lequel nous pouvons effectuer les actions nécessaires.

5. Test

À des fins de test, jetons un coup d'œil à la classe VisitorDemo :

public class VisitorDemo { public static void main(String[] args) { Visitor v = new ElementVisitor(); Document d = new Document(generateUuid()); d.elements.add(new JsonElement(generateUuid())); d.elements.add(new JsonElement(generateUuid())); d.elements.add(new XmlElement(generateUuid())); d.accept(v); } // ... }

Tout d'abord, nous créons un Element Visitor, il contient l'algorithme que nous appliquerons à nos éléments.

Ensuite, nous configurons notre document avec les composants appropriés et appliquons le visiteur qui sera accepté par chaque élément d'une structure d'objet.

La sortie serait comme ceci:

processing a JSON element with uuid: fdbc75d0-5067-49df-9567-239f38f01b04 processing a JSON element with uuid: 81e6c856-ddaf-43d5-aec5-8ef977d3745e processing an XML element with uuid: 091bfcb8-2c68-491a-9308-4ada2687e203

Il montre que le visiteur a visité chaque élément de notre structure, en fonction du type d' élément , il a envoyé le traitement à la méthode appropriée et pourrait récupérer les données de chaque objet sous-jacent.

6. Inconvénients

Comme chaque modèle de conception, même le visiteur a ses inconvénients, en particulier, son utilisation rend plus difficile la maintenance du code si nous devons ajouter de nouveaux éléments à la structure de l'objet.

Par exemple, si nous ajoutons un nouveau YamlElement, nous devons mettre à jour tous les visiteurs existants avec la nouvelle méthode souhaitée pour traiter cet élément. Suite à cela, si nous avons dix visiteurs concrets ou plus, il peut être fastidieux de tous les mettre à jour.

En dehors de cela, lors de l'utilisation de ce modèle, la logique métier liée à un objet particulier est répartie sur toutes les implémentations de visiteurs.

7. Conclusion

Le modèle Visiteur est idéal pour séparer l'algorithme des classes sur lesquelles il opère. De plus, cela facilite l'ajout d'une nouvelle opération, simplement en fournissant une nouvelle implémentation du visiteur.

De plus, nous ne dépendons pas des interfaces des composants, et s'ils sont différents, c'est très bien, car nous avons un algorithme séparé pour le traitement par élément concret.

De plus, le visiteur peut éventuellement agréger des données en fonction de l'élément qu'il traverse.

Pour voir une version plus spécialisée du modèle de conception de visiteur, consultez le modèle de visiteur dans Java NIO - l'utilisation du modèle dans le JDK.

Comme d'habitude, le code complet est disponible sur le projet Github.