Aller au contenu

Patrons de conception/Visiteur

Un livre de Wikilivres.
Patron de conception
Catégorie : « Gang of Four »Comportement
Nom français : Visiteur
Nom anglais : Visitor
Découpler classes et traitements, afin de pouvoir ajouter de nouveaux traitements sans ajouter de nouvelles méthodes aux classes existantes


Un visiteur est le nom d'une des structures de patron de conception comportemental.

Le visiteur est une manière de séparer un algorithme d'une structure de données. Un visiteur possède une méthode par type d'objet traité. Pour ajouter un nouveau traitement, il suffit de créer une nouvelle classe dérivée de la classe Visiteur. On n'a donc pas besoin de modifier la structure des objets traités, contrairement à ce qu'il aurait été obligatoire de faire si on avait implémenté les traitements comme des méthodes de ces objets.

L'avantage du patron visiteur est qu'un visiteur peut avoir un état. Ce qui signifie que le traitement d'un type d'objet peut différer en fonction de traitements précédents. Par exemple, un visiteur affichant une structure arborescente peut présenter les nœuds de l'arbre de manière lisible en utilisant une indentation dont le niveau est stocké comme valeur d'état du visiteur.

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

void ObjetDeType1::accept( Visitor * v )
{
    v->visitObjetDeType1( this );
}

Créons la classe Visitor, 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
}

L'exemple suivant montre comment afficher un arbre de nœuds (les composants d'une voiture). Au lieu de créer des méthodes d'affichage pour chaque sous-classe (Wheel, Engine, Body, et Car), une seule classe est créée (CarElementPrintVisitor) pour afficher les éléments. Parce que les différentes sous-classes requièrent différentes actions pour s'afficher proprement, la classe CarElementPrintVisitor répartit l'action en fonction de la classe de l'argument qu'on lui passe.

interface CarElementVisitor
{
    void visit(Wheel wheel);
    void visit(Engine engine);
    void visit(Body body);
    void visitCar(Car car);
}

interface CarElement
{
    void accept(CarElementVisitor visitor);
    // Méthode à définir par les classes implémentant CarElements
}

class Wheel implements CarElement
{
    private String name;

    Wheel(String name)
    {
        this.name = name;
    }

    String getName()
    {
        return this.name;
    }

    public void accept(CarElementVisitor visitor)
    {
        visitor.visit(this);
    }
}

class Engine implements CarElement
{
    public void accept(CarElementVisitor visitor)
    {
        visitor.visit(this);
    }
}

class Body implements CarElement
{
    public void accept(CarElementVisitor visitor)
    {
        visitor.visit(this);
    }
}

class Car
{
    CarElement[] elements;

    public CarElement[] getElements()
    {
        return elements.clone(); // Retourne une copie du tableau de références.
    }

    public Car()
    {
        this.elements = new CarElement[]
            {
                new Wheel("front left"),
                new Wheel("front right"),
                new Wheel("back left"),
                new Wheel("back right"),
                new Body(),
                new Engine()
            };
    }
}

class CarElementPrintVisitor implements CarElementVisitor
{
    public void visit(Wheel wheel)
    {
        System.out.println("Visiting "+ wheel.getName() + " wheel");
    }

    public void visit(Engine engine)
    {
        System.out.println("Visiting engine");
    }

    public void visit(Body body)
    {
        System.out.println("Visiting body");
    }

    public void visitCar(Car car)
    {
        System.out.println("\nVisiting car");
        for(CarElement element : car.getElements())
        {
            element.accept(this);
        }
        System.out.println("Visited car");
    }
}

class CarElementDoVisitor implements CarElementVisitor
{
    public void visit(Wheel wheel)
    {
        System.out.println("Kicking my "+ wheel.getName());
    }

    public void visit(Engine engine)
    {
        System.out.println("Starting my engine");
    }

    public void visit(Body body)
    {
        System.out.println("Moving my body");
    }

    public void visitCar(Car car)
    {
        System.out.println("\nStarting my car");
        for(CarElement carElement : car.getElements())
        {
            carElement.accept(this);
        }
        System.out.println("Started car");
    }
}

public class VisitorDemo
{
    static public void main(String[] args)
    {
        Car car = new Car();

        CarElementVisitor printVisitor = new CarElementPrintVisitor();
        CarElementVisitor doVisitor = new CarElementDoVisitor();

        printVisitor.visitCar(car);
        doVisitor.visitCar(car);
    }
}
<?php
interface CarElement
{
    public function accept(CarElementVisitor $visitor);
}

interface CarElementVisitor
{
    public function visit(CarElement $carElement);
}

class Body implements CarElement
{
    public function accept(CarElementVisitor $visitor)
    {
        $visitor->visit($this);
    }
}

class Engine implements CarElement
{
    public function accept(CarElementVisitor $visitor)
    {
        $visitor->visit($this);
    }
}

class Wheel implements CarElement
{
    private $name;

    public function __construct(String $name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function accept(CarElementVisitor $visitor)
    {
        $visitor->visit($this);
    }
}

class Car
{
    private $elements;

    public function __construct()
    {
        $this->elements = [
                new Wheel("front left"),
                new Wheel("front right"),
                new Wheel("back left"),
                new Wheel("back right"),
                new Body(),
                new Engine()
            ];
    }

    public function getElements()
    {
        return $this->elements; // Retourne une copie du tableau de références.
    }
}

class CarElementDoVisitor implements CarElementVisitor
{
    public function visit(CarElement $carElement)
    {
        if ($carElement instanceof Wheel) {
            echo "Kicking my " . $carElement->getName() . "<br>";
        }

        if ($carElement instanceof Engine) {
            echo "Starting my engine<br>";
        }

        if ($carElement instanceof Body) {
            echo "Moving my body<br>";
        }
    }

    public function visitCar(Car $car)
    {
        echo "<br>Starting my car<br>";

        foreach ( $car->getElements() as $carElement ) {
            $carElement->accept($this);
        }

        echo "Started car<br>";
    }
}

class CarElementPrintVisitor implements CarElementVisitor
{
    public function visit(CarElement $carElement)
    {
        if ($carElement instanceof Wheel) {
            echo "Visiting  " . $carElement->getName() . "wheel<br>";
        }

        if ($carElement instanceof Engine) {
            echo "Visiting engine<br>";
        }

        if ($carElement instanceof Body) {
            echo "Visiting body<br>";
        }
    }

    public function visitCar(Car $car)
    {
        echo "Visiting car<br>";

        $elements = $car->getElements();
        foreach ( $elements as $element) {
            $element->accept($this);
        }

        echo "Visited car<br>";
    }
}

$car = new Car();
$printVisitor = new CarElementPrintVisitor();
$doVisitor = new CarElementDoVisitor();
$printVisitor->visitCar($car);
$doVisitor->visitCar($car);