Le polymorphisme
Précédemment, nous avons compris que l’héritage permettait de généraliser les attributs. De la même manière, l’héritage des méthodes permet de partager des comportements communs entre différentes classes. Ainsi, une classe fille peut hériter des méthodes de sa classe mère, ce qui évite la duplication de code, et donc encore une fois facilite la maintenance du programme. L’héritage des méthodes favorise également la modularité et la réutilisabilité du code, en permettant ainsi de créer des classes plus spécialisées à partir de classes génériques.
Cependant, il est parfois nécessaire d’adapter ou de modifier le comportement d’une méthode héritée pour répondre aux besoins spécifiques d’une classe fille. Dans ce cas, on peut utiliser la technique de la redéfinition de méthode (appelé polymorphisme), qui consiste à réécrire la méthode dans la classe fille avec la même signature que celle de la classe mère, mais avec une implémentation différente. Cette approche permet de conserver l’architecture de base tout en personnalisant le comportement des méthodes pour chaque classe dérivée.
Comment regrouper des méthodes communes dans une superclasse ?
Pour ce faire, il est suffi d’identifier les fonctions similaires ou identiques que partagent plusieurs sous-classes et de les déplacer vers une superclasse commune. Une fois la méthode déplacée dans la superclasse, il n’est plus nécessaire de la conserver dans les classes dérivées. En supprimant ces duplications de méthodes, on obtient un code plus propre et plus facile à maintenir. Cette approche garantit également qu’une modification apportée à la méthode dans la superclasse sera automatiquement répercutée sur toutes les sous-classes qui en héritent, évitant ainsi les incohérences entre les différentes implémentations.
De plus, en regroupant les méthodes communes dans la superclasse, on crée également un comportement par défaut pour les nouvelles sous-classes. Lorsqu’une nouvelle sous-classe est créée, elle héritera automatiquement de ces méthodes communes et bénéficiera ainsi du comportement par défaut défini dans la superclasse. Cette approche favorise la cohérence et l’uniformité dans le comportement des différentes sous-classes, ce qui facilite la compréhension globale du code.
Prenons l’exemple de notre hiérarchie de classes « Véhicule », « Moto » et « Voiture ». Dans cette hiérarchie, la méthode démarrer()
est une fonctionnalité commune aux deux classes dérivées, « Moto » et « Voiture ». Plutôt que de définir la méthode démarrer()
séparément dans chacune de ces classes, nous pouvons la placer dans la classe « Véhicule ». En déplaçant la méthode démarrer()
dans la classe « Véhicule », les sous-classes « Moto » et « Voiture » héritent automatiquement de cette méthode, sans qu’il soit nécessaire de la réécrire pour chacune d’elles.
class Vehicule: def __init__(self): self.__moteurAllumé = False @property def moteur(self): return self.__moteurAllumé def demarrer(self): self.__moteurAllumé = True class Voiture(Vehicule): def __init__(self): super().__init__() class Moto(Vehicule): def __init__(self): super().__init__() self.coupeCircuit = True v = Voiture() m = Moto() v.demarrer() m.demarrer() print("L'état du moteur de la voiture: ", v.moteur) print("L'état du moteur de la moto: ", m.moteur)
#include <iostream> class Vehicule { public: Vehicule() : moteurAllume(false) {} bool getMoteur() const { return moteurAllume; } void demarrer() { moteurAllume = true; } protected: bool moteurAllume; }; class Voiture : public Vehicule { public: Voiture() : Vehicule() {} }; class Moto : public Vehicule { public: Moto() : Vehicule(), coupeCircuit(true) {} bool coupeCircuit; }; int main() { Voiture v; Moto m; v.demarrer(); m.demarrer(); std::cout << "L'état du moteur de la voiture: " << std::boolalpha << v.getMoteur() << std::endl; std::cout << "L'état du moteur de la moto: " << std::boolalpha << m.getMoteur() << std::endl; return 0; }
using System; public class Vehicule { private bool _moteurAllume; public Vehicule() { _moteurAllume = false; } public bool Moteur { get { return _moteurAllume; } } public void Demarrer() { _moteurAllume = true; } } public class Voiture : Vehicule { public Voiture() : base() { } } public class Moto : Vehicule { public bool CoupeCircuit { get; set; } public Moto() : base() { CoupeCircuit = true; } } public class Program { public static void Main(string[] args) { Voiture v = new Voiture(); Moto m = new Moto(); v.Demarrer(); m.Demarrer(); Console.WriteLine("L'état du moteur de la voiture: " + v.Moteur); Console.WriteLine("L'état du moteur de la moto: " + m.Moteur); } }
Comment spécialiser une méthode ?
La spécialisation d’une méthode consiste à adapter ou modifier une méthode héritée d’une superclasse pour répondre aux besoins spécifiques d’une sous-classe. Cette technique permet de personnaliser le comportement d’une méthode tout en conservant la structure de base et la logique commune définies dans la superclasse. Pour spécialiser une méthode, on utilise généralement la technique de la surcharge dans la sous-classe.
La surcharge permet à la sous-classe de redéfinir la méthode héritée de la superclasse en fournissant une nouvelle implémentation. Cette nouvelle implémentation peut modifier, étendre ou remplacer entièrement la logique de la méthode originale. Dans certains cas, la sous-classe peut également faire appel à la méthode originale de la superclasse, ce qui permet de conserver la logique de base tout en ajoutant des fonctionnalités ou des comportements spécifiques à la sous-classe.
Prenons l’exemple de notre hiérarchie de classes « Véhicule », « Moto » et « Voiture ». Comme nous l’avons vu précédemment, la méthode démarrer()
peut-être placée dans la classe « Véhicule » puisqu’elle est commune aux deux classes dérivées, « Moto » et « Voiture ». Cependant, la « Moto » a besoin de s’assurer que le coupe-circuit n’est pas activé avant de démarrer.
Dans ce cas, la classe « Moto » peut spécialiser la méthode démarrer()
en surchargeant cette méthode héritée de la classe « Véhicule ». La nouvelle implémentation de la méthode démarrer()
dans la classe « Moto » pourrait d’abord vérifier l’état du coupe-circuit. Si le coupe-circuit n’est pas activé, la méthode peut ensuite faire appel à la méthode démarrer()
originale de la superclasse. Ainsi, la logique du démarrage est conservée tout en ajoutant une vérification supplémentaire spécifique à la classe « Moto ».
class Vehicule: def __init__(self): self.__moteurAllumé = False @property def moteur(self): return self.__moteurAllumé def demarrer(self): self.__moteurAllumé = True class Voiture(Vehicule): def __init__(self): super().__init__() class Moto(Vehicule): def __init__(self): super().__init__() self.coupeCircuit = True def demarrer(self): if self.coupeCircuit is False: super().demarrer() v = Voiture() m = Moto() v.demarrer() m.demarrer() print("L'état du moteur de la voiture: ", v.moteur) print("L'état du moteur de la moto: ", m.moteur) m.coupeCircuit = False m.demarrer() print("L'état du moteur de la moto: ", m.moteur)
#include <iostream> class Vehicule { public: Vehicule() : moteurAllume(false) {} bool getMoteur() const { return moteurAllume; } virtual void demarrer() { moteurAllume = true; } protected: bool moteurAllume; }; class Voiture : public Vehicule { public: Voiture() : Vehicule() {} }; class Moto : public Vehicule { public: Moto() : Vehicule(), coupeCircuit(true) {} void demarrer() override { if (!coupeCircuit) { Vehicule::demarrer(); } } bool coupeCircuit; }; int main() { Voiture v; Moto m; v.demarrer(); m.demarrer(); std::cout << "L'état du moteur de la voiture: " << v.getMoteur() << std::endl; std::cout << "L'état du moteur de la moto: " << m.getMoteur() << std::endl; m.coupeCircuit = false; m.demarrer(); std::cout << "L'état du moteur de la moto: " << m.getMoteur() << std::endl; return 0; }
using System; public class Vehicule { private bool _moteurAllume; public Vehicule() { _moteurAllume = false; } public bool Moteur { get { return _moteurAllume; } } public virtual void Demarrer() { _moteurAllume = true; } } public class Voiture : Vehicule { public Voiture() : base() { } } public class Moto : Vehicule { public bool CoupeCircuit { get; set; } public Moto() : base() { CoupeCircuit = true; } public override void Demarrer() { if (!CoupeCircuit) { base.Demarrer(); } } } public class Program { public static void Main(string[] args) { Voiture v = new Voiture(); Moto m = new Moto(); v.Demarrer(); m.Demarrer(); Console.WriteLine("L'état du moteur de la voiture: " + v.Moteur); Console.WriteLine("L'état du moteur de la moto: " + m.Moteur); m.CoupeCircuit = false; m.Demarrer(); Console.WriteLine("L'état du moteur de la moto: " + m.Moteur); } }