Aller au contenu

Utilisation du Pattern Visitor

Voici un exemple complet utilisant le pattern Visitor pour gérer les capacités des animaux (nager, voler, courir) sans utiliser de instanceof ou de casting. Ce pattern est particulièrement utile lorsqu’on a une hiérarchie de classes stable (comme Animal) et que l’on veut ajouter de nouvelles spécificités comme nager, voler sans modifier les classes existantes !

Structure de base avec le pattern Visitor

Étape 1 : Définir l’interface Animal et la méthode accept(). Chaque classe concrète d’animal devra implémenter une méthode accept() qui permettra au visiteur de visiter l’animal.

// interface Animal avec la méthode accept()
public interface Animal {
    void accept(AnimalVisitor visitor);
}

Étape 2 : Définir l’interface AnimalVisitor

Cette interface définit les méthodes de visite pour chaque type d’animal concret.

// interface du visiteur
public interface AnimalVisitor {
    void visit(Chien chien);
    void visit(Oiseau oiseau);
    void visit(Poisson poisson);
}

Étape 3 : Implémenter les classes concrètes d’animaux

Chaque classe d’animal implémente Animal et la méthode accept().

Classe Chien :

public class Chien implements Animal {
    private String nom;

    public Chien(String nom) {
        this.nom = nom;
    }

    @Override
    public void accept(AnimalVisitor visitor) {
        visitor.visit(this);  // passe l'instance courante au visiteur
    }

    public void nager() {
        System.out.println(nom + " nage en battant des pattes !");
    }

    public void courir() {
        System.out.println(nom + " court à quatre pattes !");
    }

    public String getNom() {
        return nom;
    }
}

Classe Oiseau :

public class Oiseau implements Animal {
    private String nom;

    public Oiseau(String nom) {
        this.nom = nom;
    }

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

    public void voler() {
        System.out.println(nom + " vole dans le ciel !");
    }

    public void courir() {
        System.out.println(nom + " saute en marchant !");
    }

    public String getNom() {
        return nom;
    }
}

Classe Poisson :

public class Poisson implements Animal {
    private String nom;

    public Poisson(String nom) {
        this.nom = nom;
    }

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

    public void nager() {
        System.out.println(nom + " nage en ondulant !");
    }

    public String getNom() {
        return nom;
    }
}

Étape 4 : Implémenter les visiteurs concrets

Chaque visiteur concret implémente AnimalVisitor et définit le comportement pour chaque type d’animal.

public class NagerVisitor implements AnimalVisitor {
    @Override
    public void visit(Chien chien) {
        chien.nager();  // Un chien peut nager
    }

    @Override
    public void visit(Oiseau oiseau) {
        // Un oiseau ne nage pas (sauf exception, comme un manchot)
        System.out.println(oiseau.getNom() + " ne sait pas nager !");
    }

    @Override
    public void visit(Poisson poisson) {
        poisson.nager();  // Un poisson peut nager
    }
}

Visiteur pour faire voler les animaux :

public class VolerVisitor implements AnimalVisitor {
    @Override
    public void visit(Chien chien) {
        // Un chien ne vole pas
        System.out.println(chien.getNom() + " ne sait pas voler !");
    }

    @Override
    public void visit(Oiseau oiseau) {
        oiseau.voler();  // Un oiseau peut voler
    }

    @Override
    public void visit(Poisson poisson) {
        // Un poisson ne vole pas
        System.out.println(poisson.getNom() + " ne sait pas voler !");
    }
}

Visiteur pour faire courir les animaux :

public class CourirVisitor implements AnimalVisitor {
    @Override
    public void visit(Chien chien) {
        chien.courir();  // chien peut courir
    }

    @Override
    public void visit(Oiseau oiseau) {
        oiseau.courir();  // oiseau peut "courir" (sautiller)
    }

    @Override
    public void visit(Poisson poisson) {
        // poisson ne court pas
        System.out.println(poisson.getNom() + " ne sait pas courir !");
    }
}

Étape 5 : Utilisation dans la classe Main

public class Main {
    public static void main(String[] args) {
        // liste d'animaux
        List<Animal> animaux = new ArrayList<>();
        animaux.add(new Chien("Rex"));
        animaux.add(new Oiseau("Piaf"));
        animaux.add(new Poisson("Nemo"));

        //  visiteurs
        AnimalVisitor nagerVisitor = new NagerVisitor();
        AnimalVisitor volerVisitor = new VolerVisitor();
        AnimalVisitor courirVisitor = new CourirVisitor();

        // application des visiteurs à tous les animaux
        System.out.println("=== Ceux qui nagent ===");
        for (Animal animal : animaux) {
            animal.accept(nagerVisitor);
        }

        System.out.println("\n=== Ceux qui volent ===");
        for (Animal animal : animaux) {
            animal.accept(volerVisitor);
        }

        System.out.println("\n=== Ceux qui courent ===");
        for (Animal animal : animaux) {
            animal.accept(courirVisitor);
        }
    }
}

Résultat attendu :

=== Nageurs ===
Rex nage en battant des pattes !
Piaf ne sait pas nager !
Nemo nage en ondulant !

=== Voleurs ===
Rex ne sait pas voler !
Piaf vole dans le ciel !
Nemo ne sait pas voler !

=== Coureurs ===
Rex court à quatre pattes !
Piaf saute en marchant !
Nemo ne sait pas courir !

Avantages

Les inconvénients

Quand utiliser ce pattern ?

Ne pas utiliser quand la hiérarchie de classes change souvent (nouvelle classe d’animal fréquente) et les opérations sont simples et peu nombreuses (dans ce cas, instanceof ou une méthode générique comme faireNager() peut suffire).

Animal (interface)
│
├── Chien
├── Oiseau
└── Poisson
    │
    ▼
AnimalVisitor (interface)
│
├── NagerVisitor
├── VolerVisitor
└── CourirVisitor

Autre exemple plus simple

On va imaginer qu’un réparateur fait des choses différentes selon les objets, ici, des jouets cassés.

Votre enfant a une boîte de jouets cassés avec :

Vous, le “Visteur” alias le réparateur, vous allez :

Chaque jouet accepte la visite du réparateur, mais le réparateur fait quelque chose de différent pour chaque type de jouet de votre enfant !

En java on pourrait écrire :

Interface :

// interface pour les jouets (ils "acceptent" un visiteur)
interface Jouet {
    void accepter(Visiteur visiteur);
}

Classe Voiture :

// différents jouets
class Voiture implements Jouet {
    public void accepter(Visiteur visiteur) {
        visiteur.visiter(this); // "Moi, la voiture, je me fais visiter !"
    }
}

Classe Avion :

class Avion implements Jouet {
    public void accepter(Visiteur visiteur) {
        visiteur.visiter(this); // "Moi, l'avion, je me fais visiter !"
    }
}

Classe Robot :

class Robot implements Jouet {
    public void accepter(Visiteur visiteur) {
        visiteur.visiter(this); // "Moi, le robot, je me fais visiter !"
    }
}

Interface Visiteur :

// interface du visiteur (ce qu'il fait pour chaque jouet)
interface Visiteur {
    void visiter(Voiture voiture);
    void visiter(Avion avion);
    void visiter(Robot robot);
}

Classe Réparateur :

// visiteur concret : le réparateur (vous)
class Reparateur implements Visiteur {
    
    public void visiter(Voiture voiture) {
        System.out.println("Je répare la voiture : je change les roues ! "");
    }

    public void visiter(Avion avion) {
        System.out.println("Je répare l'avion : je vérifie les ailes ! ");
    }

    public void visiter(Robot robot) {
        System.out.println("Je répare le robot : je change les piles ! ");
    }
}

Classe Main :

// Utilisation
public class Main {
    public static void main(String[] args) {
        Jouet[] boiteAJouets = {new Voiture(), new Avion(), new Robot()};
        Visiteur reparateur = new Reparateur();

        for (Jouet jouet : boiteAJouets) {
            jouet.accepter(reparateur); // Chaque jouet se fait réparer différemment !
        }
    }
}