Design pattern: Visiteur

Objectif:

Permettre d’appliquer des comportements spécifiques à un ou plusieurs objets et être sûr que tous les types d’objets sont pris en compte

Justification

Problèmes

On possède une liste hétérogère d’objets, par exemple une liste de véhicules: voiture, moto, etc…
On souhaite appliquer des comportements sur ces objets comme:
– "ajouter des passagers",
– "ajouter des bagages".

Certains problèmes se posent:
Comportement spécifique à un type de véhicule: chaque comportement est applicable à tous les types de véhicule mais chaque comportement est spécifique au type de véhicule: par exemple on ne peut pas ajouter plus d’un passager à une moto ou plus de 4 passagers à une voiture.
Ajout d’un nouveau comportement: si on considère un nouveau comportement comme "ajouter un bagage" comment être sûr que ce nouveau comportement sera implémenté pour tous les types de véhicules.
Ajout d’un nouveau type d’objet: si on ajoute un nouveau véhicule, comment être sûr qu’il sera pris en compte pour tous les comportements.
Implémentation du comportement: pour éviter de casser le principe de la responsabilité unique, on ne veut pas modifier objets à chaque ajout d’un nouveau comportement. Le comportement doit donc être implémenté dans une classe séparée.

Solution

Le pattern "visiteur" répond à ce problème en considérant:
des objets visités: dans notre exemple, ce sont les véhicules.
des visiteurs: dans notre exemple, ce sont les comportements.
Pour permettre l’application des comportements, les objets visités comportent une fonction permettant "d’accepter" la visite des visiteurs: AcceptNewLoad().

Véhicules visités

Voici un exemple d’implémentation des objets visités:

public abstract class Vehicle
{
    public Vehicle()
    {
        this.PassagerCount = 0;
        this.TotalLuggageWeight = 0;
    }

    public abstract void AcceptNewLoad(ILoadHandler loadHandler);

    public int PassagerCount { get; set; }
    public int TotalLuggageWeight { get; set; }
}

public class Car : Vehicle
{
    public override void AcceptNewLoad(ILoadVisitor loadHandler)
    {
        loadHandler.VisitForAddingLoad(this); 
    } 
}

public class Motocycle : Vehicle
{
    public override void AcceptNewLoad(ILoadVisitor loadHandler)
    {
        loadHandler.VisitForAddingLoad(this); 
    }
}

Visiteurs

Un exemple des visiteurs:

public interface ILoadHandler
{
    void VisitForAddingLoad(Car car);
    void VisitForAddingLoad(Motocycle moto);
}

public class NewPassagerLoadHandler : ILoadHandler
{
    public void VisitForAddingLoad(Car car)
    {
        if (car.PassagerCount < 4)
            car.PassagerCount++; 
    }

    public void VisitForAddingLoad(Motocycle moto)
    {
        if (moto.PassagerCount < 2)
            moto.PassagerCount++; 
    }
}

public class NewLuggageLoadHandler : ILoadHandler
{
    private int _luggageWeight;

    public NewLuggageLoadHandler(int luggageWeight)
    {
        this._luggageWeight = luggageWeight;
    }

    public void VisitForAddingLoad(Car car)
    {
        if (this.luggageWeight < 50 && car.TotalLuggageWeight < 200)
            car.TotalLuggageWeight += this._luggageWeight; 
    }

    public void VisitForAddingLoad(Motocycle moto)
    {
        if (this.luggageWeight < 5 && moto.TotalLuggageWeight < 15)
            moto.TotalLuggageWeight += this._luggageWeight; 
    }
}

Utilisation des objets

var addPassagerVisitor = new NewPassagerLoadHandler();
var addLuggageVisitor = new NewLuggageLoadHandler(15);

foreach (var vehicle in Vehicles)
{
    vehicle.AcceptNewLoad(addPassagerVisitor);
    vehicle.AcceptNewLoad(addLuggageVisitor);
}

Pour aller plus loin…

Dépendances entre les objets

– "Visiteur" évite de lier les objets visités aux classes spécialisées de visiteurs puisque la seule dépendance est sur l’interface des visiteurs ILoadHandler.
– L’implémentation des objets visités (Vehicle) n’est pas modifiée en cas d’ajout ou de modification d’un visiteur.
En cas d’ajout d’un visiteur, l’objet visité doit hériter de Vehicle ce qui garantit l’implémentation de AcceptNewLoad() (point d’entrée des visiteurs). De même le visiteur doit satisfaire l’interface ILoadHandler donc tous les types d’objets visités seront pris en compte par les implémentations différentes de VisitForAddingLoad.
En cas d’ajout d’un objet visité, l’interface des visiteurs ILoadHandler devra être perfectionnée en rajoutant la signature:

void VisitForAddingLoad(NewVehicle newVehicle);

De fait, tous les visiteurs devront être modifiés et prendront en compte le nouvel objet. Du coté de l’objet visité, pas de changement.

Diagramme théorique

Limite

L’implémentation de ce pattern nécessite une organisation des classes sur lequel il est difficile de revenir par la suite:
– Les objets visités doivent tous hérités la classe abstraite VisitedObject,
– Tous les visiteurs doivent implémenter toutes les surcharges de la fonction Visit() correspondant aux différents types d’objets visités.
Il sera difficile et couteux d’envisager un autre pattern au cas où celui-ci ne convient plus. Il est donc préférable de limiter son utlisation à un contexte précis et un faible nombre de classes.

Leave a Reply