Visitor

La programmation avec design patterns

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Dans la vie on a parfois besoin d'aller voir une chose sans forcément influencer sa façon d'être. On ira voir une exposition sans forcément voler un tableau. Dans le monde de développement, le même paradigme a lieu. Il s'appelle visiteur.

Le design pattern qu'on abordera dans cet article s'appelle visitor (visiteur). D'abord on verra son objectif et ses avantages. Après on montrera comment il fonctionne sur l'exemple du monde réel traduit dans le monde informatique.

Le visiteur, comme son nom indique, permet d'inspecter des propriétés des autres classes. On dit alors que l'algorithme de traitement est séparé de la structure des données sur laquelle il opère. Cette isolation facilite donc les opérations effectuées sur les données d'une classe. Elles se font alors à l'extérieur de la structure des données et peuvent être faites par un ou plusieurs autres visiteurs.

De ce court descriptif on déduit que 4 acteurs participent dans ce patron de conception :
- visitor (visiteur) : une interface ou une classe abstraite qui détermine toutes les opérations pouvant être effectuées sur les classes qu'on peut visiter.
- concrete visitor (visiteur concrète) : l'implémentation concrète de l'interface ou de la classe abstraite du visiteur.
- visitable : l'interface dans laquelle on définit la méthode qui acceptera (ou pas) les visites des visiteurs.
- concrete visitable (l'endroit visitable) : l'implémentation de visitable où l'on détermine le processus d'accès à la classe visitable.

Ce patron de conception est utile dans les situations où l'on a plusieurs opérations distinctes et non liées à effectuer à travers des données d'une classe visitable. Grâce au visiteur, on n'est pas obligés de modifier le code de la classe visitable car le traitement se fait du côté du visiteur. La séparation algorithme - données garantit que le code de l'application reste clair.

La séparation des couche permet aussi à des équipes différentes travailler sur la même structure des données. Elles peuvent alors se contenter d'inspecter la structure, de récupérer les données intéressantes et ensuite faire le traitement du code de leurs visiteurs. C'est plus facile à gérer que la manipulation d'un seul groupe des données par plusieurs développeurs en même temps.

En plus de cela, on peut imaginer que les classes visitables n'acceptent pas tous les visiteurs. Alors leurs méthodes accept() permettront de vérifier si le visiteur a suffisamment de permissions pour pouvoir manipuler leur structure des données.

Résumons maintenant tout cela au sein d'un exemple. L'exemple présentera deux médecins qui vont vérifier l'état de santé d'un patient. Un des médecins a une bonne réputation et il saura détecter l'état d'un organe. L'autre n'en sera pas capable.

Exemple du visitor

interface Visitable{
public void accept(Visitor visitor);

public double getHealth();

public String getName();
}

class Eyes implements Visitable{
private final double health;

public Eyes(final double health){
this.health=health;
}

@Override
public void accept(final Visitor visitor){
visitor.visit(this);
}

@Override
public double getHealth(){
return this.health;
}

@Override
public String getName(){
return "Eyes";
}
}

class Intestine implements Visitable{
private final double health;

public Intestine(final double health){
this.health=health;
}

@Override
public void accept(final Visitor visitor){
visitor.visit(this);
}

@Override
public double getHealth(){
return this.health;
}

@Override
public String getName(){
return "Intestine";
}
}

class Hand implements Visitable{
private final double health;

public Hand(final double health){
this.health=health;
}

@Override
public void accept(final Visitor visitor){
visitor.visit(this);
}

@Override
public double getHealth(){
return this.health;
}

@Override
public String getName(){
return "Hand";
}
}

Dans cette partie on présente les parties visitables. Elles ne sont composées que de simples getters pour faciliter la compréhensions. On y remarque les yeux, l'intestin et la main. La méthode accept() permet au médecin de visiter cette partie de corps. Si par exemple, on aurait deux métiers de médecin, un ophtalmologue et un interniste, on pourrait bloquer l'accès aux yeux à l'interniste et le rendre possible à l'ophtalmologiste. Cette autorisation pourrait très bien se faire à l'intérieur de la méthode accept(), avant d'appeler visitor.visit(this).


class Body implements Visitable{
private final Visitable[] bodyParts;

public Body(){
this.bodyParts=new Visitable[] {new Eyes(30.0d), new Intestine(50.0d), new Hand(80.0d)};
}

@Override
public void accept(final Visitor doctor){
for(final Visitable part : this.bodyParts){
doctor.visit(part);
}
}

@Override
public double getHealth(){
double global=0.0d;
for(final Visitable part : this.bodyParts){
global+=part.getHealth();
}
return global/this.bodyParts.length;
}

@Override
public String getName(){
return "Body";
}
}

La classe Body est une interface visitable qui regroupe toutes les parties présentées précédemment. Le visiteur accédéra alors à la méthode visit() qui parcourira chaque partie de corps et forcera le médecin d'aller la visiter.


interface Visitor{
public void visit(Visitable visitable);
}

class IncompetentDoctor implements Visitor{
@Override
public void visit(final Visitable visitable){
if(visitable.getHealth()>10.0d){
System.out.println(visitable.getName()+" is good. It not needs to be treated");
}
else{
System.out.println(visitable.getName()+" is not good. It needs to be treated");
}
}
}

class CompetentDoctor implements Visitor{
@Override
public void visit(final Visitable visitable){
if(visitable.getHealth()>70.0d){
System.out.println(visitable.getName()+" is good. It not needs to be treated");
}
else{
System.out.println(visitable.getName()+" is not good. It needs to be treated");
}
}
}

Dans ce code on voit les deux médecins : un qui est compétent et l'autre qui est incompétent. On voit dans les deux méthode de visitation, appelées visit(), que les deux n'estiment pas l'état de l'organe de la même manière. Le mauvais juge que leur état est correct à partir de 10%, tandis que l'autre exige 70% pour dire qu'une partie de corps est saine.


final Body body=new Body();
System.out.println("Bad doctor says :");
body.accept(new IncompetentDoctor());
System.out.println("Good doctor says :");
body.accept(new CompetentDoctor());

Ce court code présente comment fonctionne le client pour le design pattern visiteur. Tout d'abord on initialise le groupe des objets visitables (corps) et ensuite on fait l'inspecter par chaque médecin. Voici le résultat de cette opération :


Bad doctor says :
Eyes is good. It not needs to be treated
Intestine is good. It not needs to be treated
Hand is good. It not needs to be treated
Good doctor says :
Eyes is not good. It needs to be treated
Intestine is not good. It needs to be treated
Hand is good. It not needs to be treated


Sur notre exemple médical on a vu que le visiteur peut nous permettre de séparer la structure des données d'entrée de l'opération effectuée sur ces données. On sait maintenant que si demain un troisième médecin, très exigeant, arrive, on n'aura aucune difficulté à l'introduire. Pareil si l'on voudrait permettre l'inspection des organes en fonction du métier du médecin (les yeux pour l'ophtalmologiste, l'intestin pour l'interniste etc.).

The pattern should be used when you have distinct and unrelated operations to perform across a structure of objects. This avoids adding in code throughout your object structure that is better kept seperate, so it encourages cleaner code. You may want to run operations against a set of objects with different interfaces. Visitors are also valuable if you have to perform a number of unrelated operations across the classes.

In summary, if you want to decouple some logical code from the elements that you're using as input, visitor is probably the best pattern for the job.

Pour ajouter un traitement à notre application, il suffit donc de créer un nouveau Visiteur avant de surcharger les méthodes permettant de visiter les différents objets de la hierarchie. Ensuite, pour utiliser le visiteur, on fait:
Application d'un visiteur
Sélectionnez

monObjet->accept( monVisiteur ) ;

De cette manière, on peut facilement ajouter de nouveaux traitements sans toucher à la hiérarchie de nos objets (en POO "classique", on aurait implémenté de nouvelles méthodes pour ajouter de nouvelles fonctionnalités). Grâce aux Visiteurs :

le code est plus clair (des fonctionnalités différentes se trouvent dans des Visiteurs différents)
des équipes différentes peuvent travailler sur des fonctionnalités différentes sans gêner les autres équipes
on n'est pas obligé de tout recompiler à chaque ajout d'une fonctionnalité (seul le code du Visiteur est recompilé)



From Wikipedia we read: In object-oriented programming and software engineering, the Visitor Design Pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures.

Let's clarify a little bit this definition: In object-oriented programming and software engineering, the Visitor Pattern is a way of separating a computation (or operation) from the data structure it operates on.
A practical result of this separation is the ability to add new operations to existing data structures without modifying those data structures.

In particular, we will employ the word computation instead of algorithm because the former is more restrictive and more specific than the later. This article shows that obtaining elements from a data structure also involves an algorithm, which could cause confusion. For this reason, it's better to avoid the word algorithm at all.

++++++++ dans
@Override
public void accept(final Visitor visitor){
visitor.visit(this);
}
on peut rajouter un contrôle d'accès ou alors retourner des résultats différents en fonction du médecin (visitor.getReputation() > 50, return résultat vrai, else return résultat majoré)

http://rgomes-info.blogspot.co.uk/2013/01/a-better-implementation-of-visitor.html
Remember: a Visitable defines data access

The idea is that we need an interface which defines a how data can be obtained in general.

Once the interface is defined, we then define a class which knows how data can be obtained from a given data structure in particular. It means to say that, if we have several different data structures, we will need several classes implementing the Visitable interface, one class for each data structure involved.

Notice that nothing was said about the computation. It's not responsibility of interface Visitable anything involving the computation: it only cares about how single data elements can be obtained from a given data structure.

For the sake of brevity, lets present the Visitable interface and demonstrate only one of the several possible data access expedients we may eventually need:

+++ participants
Design participants/components

The participants classes in this pattern are:

Visitor – This is an interface or an abstract class used to declare the visit operations for all the types of visitable classes.

ConcreteVisitor – For each type of visitor all the visit methods, declared in abstract visitor, must be implemented. Each Visitor will be responsible for different operations.

Visitable – is an interface which declares the accept operation. This is the entry point which enables an object to be “visited” by the visitor object.

ConcreteVisitable – Those classes implements the Visitable interface or class and defines the accept operation. The visitor object is passed to this object using the accept operation.

++++++ code séparé, donc plus propre, donc le couplage moins fort
Where Would I Use This Pattern?

The pattern should be used when you have distinct and unrelated operations to perform across a structure of objects. This avoids adding in code throughout your object structure that is better kept seperate, so it encourages cleaner code. You may want to run operations against a set of objects with different interfaces. Visitors are also valuable if you have to perform a number of unrelated operations across the classes.

In summary, if you want to decouple some logical code from the elements that you're using as input, visitor is probably the best pattern for the job.
http://java.dzone.com/articles/design-patterns-visitor


"Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates."
http://blog.coreycoogan.com/2009/06/16/visitor-pattern-real-world-example/

http://manski.net/2013/05/the-visitor-pattern-explained/#object_structure


interface Visitable{
public void accept(Visitor visitor);

public double getHealth();

public String getName();
}

class Eyes implements Visitable{
private final double health;

public Eyes(final double health){
this.health=health;
}

@Override
public void accept(final Visitor visitor){
visitor.visit(this);
}

@Override
public double getHealth(){
return this.health;
}

@Override
public String getName(){
return "Eyes";
}
}

class Intestine implements Visitable{
private final double health;

public Intestine(final double health){
this.health=health;
}

@Override
public void accept(final Visitor visitor){
visitor.visit(this);
}

@Override
public double getHealth(){
return this.health;
}

@Override
public String getName(){
return "Intestine";
}
}

class Hand implements Visitable{
private final double health;

public Hand(final double health){
this.health=health;
}

@Override
public void accept(final Visitor visitor){
visitor.visit(this);
}

@Override
public double getHealth(){
return this.health;
}

@Override
public String getName(){
return "Hand";
}
}



class Body implements Visitable{
private final Visitable[] bodyParts;

public Body(){
this.bodyParts=new Visitable[] {new Eyes(30.0d), new Intestine(50.0d), new Hand(80.0d)};
}

@Override
public void accept(final Visitor doctor){
for(final Visitable part : this.bodyParts){
doctor.visit(part);
}
}

@Override
public double getHealth(){
double global=0.0d;
for(final Visitable part : this.bodyParts){
global+=part.getHealth();
}
return global/this.bodyParts.length;
}

@Override
public String getName(){
return "Body";
}
}



interface Visitor{
public void visit(Visitable visitable);
}

class IncompetentDoctor implements Visitor{
@Override
public void visit(final Visitable visitable){
if(visitable.getHealth()>10.0d){
System.out.println(visitable.getName()+" is good. It not needs to be treated");
}
else{
System.out.println(visitable.getName()+" is not good. It needs to be treated");
}
}
}

class CompetentDoctor implements Visitor{
@Override
public void visit(final Visitable visitable){
if(visitable.getHealth()>70.0d){
System.out.println(visitable.getName()+" is good. It not needs to be treated");
}
else{
System.out.println(visitable.getName()+" is not good. It needs to be treated");
}
}
}



final Body body=new Body();
System.out.println("Bad doctor says :");
body.accept(new IncompetentDoctor());
System.out.println("Good doctor says :");
body.accept(new CompetentDoctor());



Bad doctor says :
Eyes is good. It not needs to be treated
Intestine is good. It not needs to be treated
Hand is good. It not needs to be treated
Good doctor says :
Eyes is not good. It needs to be treated
Intestine is not good. It needs to be treated
Hand is good. It not needs to be treated


Le cas sans visiteur serait trop illisible. On serait peut-être obligés de faire les opérations de vérification directement dans l'implémentations du Visitable. Pour l'illustrer, on pourrait faire visit(Eyes eyes), visit(Hand hand) et y placer les conditions différentes.
Bartosz KONIECZNY 16-06-2014 06:27 design patterns
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony1

Comment inclure un template commun pour plusieurs modules ?

Le fichier à inclure (par exemple _menu.php) devrait être stocké dans le répertoire templates de l'application en question.

Ensuite, dans notre fichier de layout (par exemple layout.php), il suffit d'appeler le helper include_partial : Le répertoire global signifie que le template est global et n'appartient pas à un module particulier.