Le principe de l'évolution est beaucoup plus rapide en informatique que chez le bipède.
C’est un petit pattern, très simple, qui peut dans certaines situations vous éviter quelque saletés tel qu’un down-casting, du code déplacé ou autres copier-coller
Le problème:
Imaginons une classe ‘Personne’ qui ne possède qu’une fonction : discuteAvec(…)
La classe ‘Nommée’ en hérite et s’enrichie d’un nom.
La classe ‘Agée’ hérite elle aussi de ‘Personne’ mais celle-ci possède un age.
Maintenant, si vous êtes comme moi un adept du polymorphisme (sinon tant pis pour vous^), et que vous manipuler donc uniquement des ‘Personnes’, comment va-t-on gérer/accéder aux classes spécialisées dans notre fonction ‘disctuteAvec(…)’?
Le Double-Dispatch:
Le principe :
Notre classe de base ‘Personne’ va définir 3 fonctions virtuelles (pures):
* discuteAvec(Personne& personne) : Nous n’avons toujours pas d’informations sur le nom/age…
* discuteAvec(Nommee& nommee) : Nous pourrons accéder au nom de la personne.
* discuteAvec(Agee& agee) : Nous pourrons accéder à l’age de la personne.
Un peu de code serait probablement plus clair:
class Nommee; class Agee; // Une personne class Personne { public: // Fonction virtuelle pure: discute avec une personne dont // on ne connait ni le nom ni l'age. virtual void discuteAvec(Personne& personne) = 0; // Fonction virtuelle pure surchargée: discute avec une personne qui a un nom // (ca arrive...) virtual void discuteAvec(Nommee& nommee) = 0; // Fonction virtuelle pure surchargée: discute avec une personne qui a un age // (...parfois...) virtual void discuteAvec(Agee& agee) = 0; }; // Une personne qui a un nom class Nommee : public Personne { public: // le nom std::string nom; // Constructeur Nommee(const std::string& _nom): nom(_nom) {} // on redéfinit les fonction virtuelle (voir implémentation) virtual void discuteAvec(Personne& personne); virtual void discuteAvec(Nommee& nommee); virtual void discuteAvec(Agee& agee); }; // Une personne qui a un age class Agee : public Personne { public: // L'age unsigned short age; // (pas encore en 64 bits...) // Constructeur Agee(const unsigned short _age): age(_age) {} // on redéfinit les fonction virtuelle (voir implémentation) virtual void discuteAvec(Personne& personne); virtual void discuteAvec(Nommee& nommee); virtual void discuteAvec(Agee& agee); };
Et ‘discuteAvec(Personne& personne)’, ca ne sert a rien?!
Oh que si! C’est même la clef de voute de tout le pattern! Et cette fonction est pourtant très simple!
En fait, elle appelle juste la fonction ‘discuteAvec(*this)’ de ’personne’ (le paramêtre). Le type de l’objet (*this) est donc bien transmit et la fonction virtuelle ainsi appelée sera bien celle surchargée.
Implémentation des fonctions:
// Quand on ne connait pas le nom ou l'age de la personne void Nommee::discuteAvec(Personne& personne) { // On appel SA fonction surchargée personne.discuteAvec(*this); } // Et cette même fonction, implémentée de manière indentique, sera // présente dans toutes les classes que l'on souhaite faire intéragir // Quand on connait son nom void Nommee::discuteAvec(Nommee& nommee) { std::cout << nom << " discute avec " << nommee.nom << "." << std::endl; } // Quand on connait son age void Nommee::discuteAvec(Agee& agee) { std::cout << nom << " discute avec quelqu'un qui a " << agee.age << " ans!" << std::endl; } // Fonction identique à celle de la classe 'Nommee' void Agee::discuteAvec(Personne& personne) { personne.discuteAvec(*this); } // Quand on connait son nom void Agee::discuteAvec(Nommee& nommee) { // La fonction est déja définit dans A nommee.discuteAvec(*this); } // Quand on connait son age void Agee::discuteAvec(Agee& agee) { std::cout << "Quelqu'un de " << age << " ans discute avec quelqu'un qui a " << agee.age << " ans!" << std::endl; }
Personne* Inconnu_1 = new Nommee("Godefroi de Montmiraille"); Personne* Inconnu_2 = new Nommee("Zigom@r"); Personne* Inconnu_3 = new Agee(122); Personne* Inconnu_4 = new Agee(21); Inconnu_1->discuteAvec(*Inconnu_2); // Zigom@r discute avec Godefroi de Montmiraille Inconnu_3->discuteAvec(*Inconnu_2); // Zigom@r discute avec quelqu'un qui a 122! Inconnu_1->discuteAvec(*Inconnu_4); // Godefroi de Montmiraille discute avec quelqu'un qui a 21! Inconnu_3->discuteAvec(*Inconnu_4); // Quelqu'un qui a 21 discute avec quelqu'un qui a 122!