Visiteur (patron de conception)

En programmation orientée objet et en génie logiciel, le patron de conception comportemental visiteur est une manière de séparer un algorithme d'une structure de données.

Définition

[modifier | modifier le code]

Le Gang of Four définit le visiteur comme:

    Représentant [ing] une opération à effectuer sur des éléments d'une structure d'objet. Visitor permet de définir une nouvelle opération sans changer les classes des éléments sur lesquels elle opère.

La nature du visiteur en fait un modèle idéal pour se connecter aux API publiques permettant ainsi à ses clients d'effectuer des opérations sur une classe en utilisant une classe "visitante" sans avoir à modifier la source[1].

patron de conception visiteur
patron de conception visiteur

Ce modèle de conception permet à une classe externe d'être informée du type exact d'instances d'un ensemble de classes. Cela permet de mettre en place un traitement adapté aux détails publics des objets en cause. Par exemple un visiteur est idéal pour réaliser un rapport.

Déporter des opérations contenues dans une classe vers une autre peut sembler mauvais au sens de la POO car l'ajout ou la modification d'une classe n’entraîne pas l'adaptation des visiteurs existants, ce modèle est utile lorsqu'on a un ensemble de classes fermées (par exemple fourni par un tiers) et que l'on veut effectuer un nouveau traitement sur ces classes.

En pratique, le modèle de conception visiteur est réalisé de la façon suivante : chaque classe pouvant être « visitée » doit mettre à disposition une méthode publique « accepter » prenant comme argument un objet du type « visiteur ». La méthode « accepter » appellera la méthode « visite » de l'objet du type « visiteur » avec pour argument l'objet visité. De cette manière, un objet visiteur pourra connaître la référence de l'objet visité et appeler ses méthodes publiques pour obtenir les données nécessaires au traitement à effectuer (calcul, génération de rapport, etc.).

En pratique un visiteur permet d'obtenir le même effet que d'ajouter une nouvelle méthode virtuelle à un ensemble de classes qui ne le permet pas.

Prenons une classe ObjetPere, de laquelle hériteront Objet1, Objet2 et Objet3, elles posséderont la méthode accept(MonVisiteur& v).

 void ObjetDeType1::accept( MonVisiteur& v ) {
   v.visitObjetDeType1( *this ) ;
 }

Créons la classe MonVisiteur, dont hériteront Visiteur1 et Visiteur2. Dans chacun de ces objets, on retrouvera une méthode visiterObjet1(Objet1& a), visiterObjet2(Objet2& b) et visiterObjet3(Objet3& c).

  void MonVisiteur::visitObjetDeType1( ObjetDeType1& objet ) {
    // Traitement d'un objet de type 1
  }
  
  void MonVisiteur::visitObjetDeType2( ObjetDeType2& objet ) {
    // Traitement d'un objet de type 2
  }
  
  void MonVisiteur::visitObjetDeType3( ObjetDeType3& objet ) {
    // Traitement d'un objet de type 3
  }

Aucune importation n'est nécessaire, il suffit de créer une interface (ou classe) visiteuse, et une pour l'objet père l'acceptant :

interface MonVisiteur {
   void visit(Objet1 objet);
   void visit(Objet2 objet);
   void visit(Objet3 objet);
}
interface ObjetPere {
   void accept(MonVisiteur v);
}
public class Objet1 implements ObjetPere {	
    public void accept(MonVisiteur v) {
        v.visit(this);
    }
}
public class Objet2 implements ObjetPere {	
    public void accept(MonVisiteur v) {
        v.visit(this);
    }
}
public class Objet3 implements ObjetPere {	
    public void accept(MonVisiteur v) {
        v.visit(this);
    }
}

Cet exemple montre comment imprimer un arbre représentant une expression numérique impliquant des littéraux et leur ajout. Le même exemple est présenté à l'aide d'implémentations classiques et dynamique.

Visiteur Classique

[modifier | modifier le code]

Un visiteur classique où les opérations d'impression pour chaque type sont implémentées dans une classe ExpressionPrinter comme un certain nombre de surcharges de la méthode Visit.

using System;
using System.Text;

namespace Wikipedia
{
    interface IExpressionVisitor
    {
        void Visit(Literal literal);
        void Visit(Addition addition);
    }

    interface IExpression
    {
        void Accept(IExpressionVisitor visitor);
    }

    class Literal : IExpression
    {
        internal double Value { get; set; }

        public Literal(double value)
        {
            this.Value = value;
        }
        public void Accept(IExpressionVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    class Addition : IExpression
    {
        internal IExpression Left { get; set; }
        internal IExpression Right { get; set; }

        public Addition(IExpression left, IExpression right)
        {
            this.Left = left;
            this.Right = right;
        }

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

    class ExpressionPrinter : IExpressionVisitor
    {
        StringBuilder sb;

        public ExpressionPrinter(StringBuilder sb)
        {
            this.sb = sb;
        }

        public void Visit(Literal literal)
        {
            sb.Append(literal.Value);
        }

        public void Visit(Addition addition)
        {
            sb.Append("(");
            addition.Left.Accept(this);
            sb.Append("+");
            addition.Right.Accept(this);
            sb.Append(")");
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            // emulate 1+2+3
            var e = new Addition(
                new Addition(
                    new Literal(1),
                    new Literal(2)
                ),
                new Literal(3)
            );
            var sb = new StringBuilder();
            var expressionPrinter = new ExpressionPrinter(sb);
            e.Accept(expressionPrinter);
            Console.WriteLine(sb);
        }
    }
}

Visiteur Dynamique

[modifier | modifier le code]

Cet exemple déclare une classe ExpressionPrinter distincte qui prend en charge l'impression. Les classes d'expression doivent exposer leurs membres pour rendre cela possible.

using System;
using System.Text;

namespace Wikipedia
{
    abstract class Expression
    {
    }

    class Literal : Expression
    {
        public double Value { get; set; }

        public Literal(double value)
        {
            this.Value = value;
        }
    }

    class Addition : Expression
    {
        public Expression Left { get; set; }
        public Expression Right { get; set; }

        public Addition(Expression left, Expression right)
        {
            Left = left;
            Right = right;
        }
    }

    class ExpressionPrinter
    {
        public static void Print(Literal literal, StringBuilder sb)
        {
            sb.Append(literal.Value);
        }

        public static void Print(Addition addition, StringBuilder sb)
        {
            sb.Append("(");
            Print((dynamic) addition.Left, sb);
            sb.Append("+");
            Print((dynamic) addition.Right, sb);
            sb.Append(")");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Emulate 1 + 2 + 3
            var e = new Addition(
                new Addition(
                    new Literal(1),
                    new Literal(2)
                ),
                new Literal(3)
            );
            var sb = new StringBuilder();
            ExpressionPrinter.Print((dynamic) e, sb);
            Console.WriteLine(sb);
        }
    }
}

Références

[modifier | modifier le code]

Lien externe

[modifier | modifier le code]