L’héritage
Dans la programmation orientée objet, l’héritage est un concept clé qui permet d’organiser les classes de manières hiérarchiques, allant des classes générales aux classes spécifiques. Cette hiérarchie facilite la création d’objets appartenant à différentes classes, tout en garantissant que les objets issus d’une classe héritent des attributs et des méthodes des classes supérieures dans la hiérarchie. Ainsi, un objet d’une classe spécifique est également un objet des classes dont il hérite, permettant à d’autres objets d’interagir avec lui en fonction du niveau hiérarchique approprié.
L’héritage procure plusieurs avantages en matière d’efficacité et de maintenabilité du code. En regroupant les caractéristiques communes à plusieurs sous-classes dans une superclasse, l’héritage aide à économiser de l’espace mémoire et du temps de traitement. En évitant la répétition des mêmes informations dans chaque sous-classe, le code devient plus fiable et facile à maintenir.
Enfin, l’héritage permet de spécialiser des classes en fonction de cas particuliers. Lorsqu’une classe est identifiée comme un cas particulier d’une classe générale, elle peut hériter de toutes les caractéristiques de cette classe générale sans avoir à les redéfinir. La classe spécifique ne sera utilisée que dans des situations où ses informations propres sont nécessaires, ce qui aide à conserver une structure de code claire et cohérente.
L’héritage est très important pour améliorer la modularité, la réutilisabilité et l’évolutivité du code en programmation orientée objet.
- La modularité est renforcée grâce à la structuration hiérarchique des classes, qui permet de diviser un programme en parties distinctes et interconnectées. Ces modules peuvent être développés, testés et débogués indépendamment les uns des autres, facilitant ainsi la gestion et la compréhension du code.
- La réutilisabilité est quant à elle favorisée par l’héritage, car il permet de réutiliser les propriétés et les méthodes définies dans une classe mère pour les classes dérivées, évitant par conséquent la duplication inutile de code.
- L’évolutivité est améliorée grâce à la capacité de l’héritage à s’adapter aux nouvelles exigences ou fonctionnalités en étendant ou modifiant simplement les classes existantes, sans perturber la structure globale du programme.
Comment regrouper des attributs communs dans une superclasse ?
L’héritage des attributs permet de définir des caractéristiques communes à plusieurs classes dans une classe supérieure et de les partager avec les classes dérivées, simplifiant ainsi la structure du code et privilégiant la réutilisabilité.
Pour illustrer le mécanisme d’héritage, prenons l’exemple du diagramme UML ci-dessous, qui décrit la relation entre les classes « Véhicule
», « Voiture
» et « Moto
». Dans cet exemple, la classe « Véhicule
» représente la classe mère, tandis que les classes « Voiture
» et « Moto
» sont les classes filles. Les attributs communs à toutes sortes de véhicules, tels que la couleur, la marque, sont regroupés dans la superclasse « Véhicule
». Cela permet aux sous-classes « Voiture
» et « Moto
» d’hériter de ces attributs sans avoir à les redéfinir.
Concernant les sous-classes, elles ne doivent contenir que des caractéristiques additionnelles ou des précisions par rapport à leur superclasse. Par exemple, la classe « Voiture
» peut inclure un attribut supplémentaire tel que le nombre de portières, spécifique aux voitures. De même, la classe « Moto
» pourrait inclure un attribut tel que l’âge minimum de conduite, qui n’est pertinent que pour les motos. Ainsi, en regroupant les attributs communs dans la superclasse et en ajoutant des attributs spécifiques dans les sous-classes, nous créons une structure de classes claire et hiérarchique qui facilite la compréhension et la maintenance du code.
class Vehicule: def __init__(self, marque, couleur): self.marque = marque self.couleur = couleur class Voiture(Vehicule): def __init__(self, marque, couleur, nb_portes): super().__init__(marque, couleur) self.nb_portes = nb_portes class Moto(Vehicule): def __init__(self, marque, couleur, age_min): super().__init__(marque, couleur) self.age_min = age_min v = Voiture("Skoda", "Grise", 5) m = Moto("Kawasaki", "Blanche", 24)
#include <iostream> #include <string> class Vehicule { public: Vehicule(std::string marque, std::string couleur) : marque(marque), couleur(couleur) {} protected: std::string marque; std::string couleur; }; class Voiture : public Vehicule { public: Voiture(std::string marque, std::string couleur, int nb_portes) : Vehicule(marque, couleur), nb_portes(nb_portes) {} private: int nb_portes; }; class Moto : public Vehicule { public: Moto(std::string marque, std::string couleur, int age_min) : Vehicule(marque, couleur), age_min(age_min) {} private: int age_min; }; int main() { Voiture v("Skoda", "Grise", 5); Moto m("Kawasaki", "Blanche", 24); return 0; }
using System; public class Vehicule { public string Marque { get; set; } public string Couleur { get; set; } public Vehicule(string marque, string couleur) { Marque = marque; Couleur = couleur; } } public class Voiture : Vehicule { public int NbPortes { get; set; } public Voiture(string marque, string couleur, int nbPortes) : base(marque, couleur) { NbPortes = nbPortes; } } public class Moto : Vehicule { public int AgeMin { get; set; } public Moto(string marque, string couleur, int ageMin) : base(marque, couleur) { AgeMin = ageMin; } } public class Program { public static void Main() { Voiture v = new Voiture("Skoda", "Grise", 5); Moto m = new Moto("Kawasaki", "Blanche", 24); } }
L’héritage et l’encapsulation
Comme vu précédemment, l’encapsulation consiste à masquer les détails internes d’une classe et à ne rendre accessibles à l’extérieur que les éléments nécessaires pour interagir avec cette classe. Dans le contexte de l’héritage, l’encapsulation permet de contrôler l’accès aux attributs et méthodes des classes mères et des classes filles. Pour cela, on utilise des modificateurs d’accès : privé, protégé et public.
Les attributs et méthodes privés sont uniquement accessibles au sein de la classe où ils sont définis, tandis que les éléments protégés sont accessibles dans la classe elle-même et dans ses sous-classes. Les éléments publics sont accessibles partout. Il est important de bien choisir les modificateurs d’accès afin de respecter les principes d’encapsulation tout en permettant l’héritage entre les classes. Ainsi, l’encapsulation et l’héritage collaborent pour créer une structure de classes bien organisée, modulaire et maintenable.
Reprenons l’exemple de la hiérarchie de classes « Véhicule
», « Voiture
» et « Moto
» en ajoutant un attribut privé, le « numéro de châssis
», à la classe « Véhicule
». Les autres attributs de la classe « Véhicule
» seront définis comme protégés.
En plaçant le numéro de châssis en mode privé, nous garantissons qu’il est en lecture seule pour toutes les classes, y compris les classes filles « Voiture
» et « Moto
». Cela renforce la sécurité, car ni les classes filles ni les classes extérieures ne peuvent modifier le numéro de châssis.
Les attributs protégés, quant à eux, peuvent être accédés et modifiés par les classes filles. Par exemple, un attribut protégé « couleur
» de la classe « Véhicule
» peut être consulté et modifié par les classes « Voiture
» et « Moto
». Cela permet aux classes filles d’hériter et d’adapter les attributs de la superclasse en fonction de leurs besoins spécifiques.
Ainsi, grâce aux modificateurs d’accès privé et protégé, nous pouvons créer une structure de classes qui respecte en même temps les principes d’encapsulation et l’héritage, tout en garantissant la sécurité et la modularité du code.
import random class Vehicule: def __init__(self, marque, couleur): self._marque = marque self._couleur = couleur self.__num_chassis = random.randint(1000, 9999) @property def marque(self): return self._marque @property def couleur(self): return self._couleur @property def num_chassis(self): return self.__num_chassis class Voiture(Vehicule): def __init__(self, marque, couleur, nb_portes): super().__init__(marque, couleur) self._nb_portes = nb_portes @property def nb_portes(self): return self._nb_portes class Moto(Vehicule): def __init__(self, marque, couleur, age_min): super().__init__(marque, couleur) self._age_min = age_min @property def age_min(self): return self._age_min v = Voiture("Skoda", "Grise", 5) m = Moto("Kawasaki", "Blanche", 24)
#include <iostream> #include <string> #include <random> class Vehicule { public: Vehicule(std::string marque, std::string couleur) : _marque(marque), _couleur(couleur), _num_chassis(rand() % 9000 + 1000) {} std::string get_marque() const { return _marque; } std::string get_couleur() const { return _couleur; } int num_chassis() const { return _num_chassis; } protected: std::string _marque; std::string _couleur; private: int _num_chassis; }; class Voiture : public Vehicule { public: Voiture(std::string marque, std::string couleur, int nb_portes) : Vehicule(marque, couleur), _nb_portes(nb_portes) {} int get_nb_portes() const { return _nb_portes; } protected: int _nb_portes; }; class Moto : public Vehicule { public: Moto(std::string marque, std::string couleur, int age_min) : Vehicule(marque, couleur), _age_min(age_min) {} int get_age_min() const { return _age_min; } protected: int _age_min; }; int main() { Voiture v("Skoda", "Grise", 5); Moto m("Kawasaki", "Blanche", 24); return 0; }
using System; class Vehicule { public Vehicule(string marque, string couleur) { _marque = marque; _couleur = couleur; _numChassis = new Random().Next(1000, 10000); } public string Marque { get { return _marque; } } public string Couleur { get { return _couleur; } } public int NumChassis { get { return _numChassis; } } protected string _marque; protected string _couleur; private int _numChassis; } class Voiture : Vehicule { public Voiture(string marque, string couleur, int nbPortes) : base(marque, couleur) { _nbPortes = nbPortes; } public int NbPortes { get { return _nbPortes; } } protected int _nbPortes; } class Moto : Vehicule { public Moto(string marque, string couleur, int ageMin) : base(marque, couleur) { _ageMin = ageMin; } public int AgeMin { get { return _ageMin; } } protected int _ageMin; } class Program { static void Main() { Voiture v = new Voiture("Skoda", "Grise", 5); Moto m = new Moto("Kawasaki", "Blanche", 24); } }
L’héritage et liens d’associations
Dans les sections précédentes, nous avons abordé la manière dont les attributs de la classe mère sont accessibles dans les classes filles lors de l’héritage, ainsi que l’utilisation de l’encapsulation pour définir le niveau de visibilité de ces attributs. Dans cette section, nous allons discuter des liens entre les classes et comment l’héritage peut influencer ces liens.
Les liens entre les classes sont généralement représentés par des attributs qui contiennent une référence vers une autre classe ou un tableau de références vers plusieurs instances de cette autre classe. Ces attributs peuvent être considérés comme des éléments hérités, tout comme les autres attributs et méthodes de la classe mère.
Puisque ces liens sont des attributs, l’héritage s’applique également à eux. Cela signifie que lorsqu’une classe fille hérite d’une classe mère, elle hérite aussi des attributs représentant les liens vers d’autres classes. Ainsi, les classes filles peuvent interagir avec les autres classes référencées par ces liens, en respectant les niveaux de visibilité définis par l’encapsulation.
Par exemple, imaginons que la classe « Véhicule
» possède un attribut « propriétaire » qui fait référence à une instance de la classe « Personne
». Lorsque les classes « Voiture
» et « Moto
» héritent de la classe « Véhicule
», elles héritent aussi de l’attribut « propriétaire » et de son lien vers la classe « Personne
». Ainsi, les instances de « Voiture
» et « Moto
» peuvent interagir avec la classe « Personne
» via l’attribut hérité « propriétaire ».
import random class Personne: def__init__(self, nom): self.nom = nom class Vehicule: def __init__(self, marque, couleur, propriétaire): self._marque = marque self._couleur = couleur self.__num_chassis = random.randint(1000, 9999) self._propriétaire = propriétaire @property def marque(self): return self._marque @property def couleur(self): return self._couleur @property def num_chassis(self): return self.__num_chassis @property def propriétaire(self): return self._propriétaire class Voiture(Vehicule): def __init__(self, marque, couleur, propriétaire, nb_portes): super().__init__(marque, couleur, propriétaire) self._nb_portes = nb_portes @property def nb_portes(self): return self._nb_portes class Moto(Vehicule): def __init__(self, marque, couleur, propriétaire, age_min): super().__init__(marque, couleur, propriétaire) self._age_min = age_min @property def age_min(self): return self._age_min s = Personne("Steve ZARETTI") v = Voiture("Skoda", "Grise", s, 5) m = Moto("Kawasaki", "Blanche", s, 24)
#include <iostream> #include <memory> #include <string> #include <random> class Personne { public: Personne(const std::string& nom) : nom(nom) {} std::string nom; }; class Vehicule { public: Vehicule(const std::string& marque, const std::string& couleur, std::shared_ptr<Personne> proprietaire) : marque(marque), couleur(couleur), num_chassis(rand() % 9000 + 1000), proprietaire(proprietaire) {} std::string getMarque() const { return marque; } std::string getCouleur() const { return couleur; } int getNumChassis() const { return num_chassis; } std::shared_ptr<Personne> getProprietaire() const { return proprietaire; } protected: std::string marque; std::string couleur; std::shared_ptr<Personne> proprietaire; private: int num_chassis; }; class Voiture : public Vehicule { public: Voiture(const std::string& marque, const std::string& couleur, std::shared_ptr<Personne> proprietaire, int nb_portes) : Vehicule(marque, couleur, proprietaire), nb_portes(nb_portes) {} int getNbPortes() const { return nb_portes; } protected: int nb_portes; }; class Moto : public Vehicule { public: Moto(const std::string& marque, const std::string& couleur, std::shared_ptr<Personne> proprietaire, int age_min) : Vehicule(marque, couleur, proprietaire), age_min(age_min) {} int getAgeMin() const { return age_min; } protected: int age_min; }; int main() { auto s = std::make_shared<Personne>("Steve ZARETTI"); auto v = std::make_shared<Voiture>("Skoda", "Grise", s, 5); auto m = std::make_shared<Moto>("Kawasaki", "Blanche", s, 24); return 0; }
using System; public class Personne { public Personne(string nom) { Nom = nom; } public string Nom { get; } } public class Vehicule { private static readonly Random random = new Random(); public Vehicule(string marque, string couleur, Personne proprietaire) { Marque = marque; Couleur = couleur; NumChassis = random.Next(1000, 10000); Proprietaire = proprietaire; } protected string Marque { get; } protected string Couleur { get; } private int NumChassis { get; } protected Personne Proprietaire { get; } } public class Voiture : Vehicule { public Voiture(string marque, string couleur, Personne proprietaire, int nbPortes) : base(marque, couleur, proprietaire) { NbPortes = nbPortes; } protected int NbPortes { get; } } public class Moto : Vehicule { public Moto(string marque, string couleur, Personne proprietaire, int ageMin) : base(marque, couleur, proprietaire) { AgeMin = ageMin; } protected int AgeMin { get; } } public class Program { public static void Main() { var s = new Personne("Steve ZARETTI"); var v = new Voiture("Skoda", "Grise", s, 5); var m = new Moto("Kawasaki", "Blanche", s, 24); } }
Résumé
Pour conclure, résumons les points clés à retenir.
- L’héritage permet d’organiser les classes de manière hiérarchique, en passant des classes générales aux classes spécifiques. Cette approche facilite la réutilisation du code, la modularité et la maintenabilité en regroupant les attributs et les liens communs dans des superclasses et en définissant des caractéristiques additionnelles ou des précisions dans les sous-classes.
- L’encapsulation est un concept étroitement lié à l’héritage. Elle permet de contrôler l’accès aux attributs et aux liens hérités en utilisant des modificateurs d’accès, tels que privé, protégé et public. L’encapsulation et l’héritage collaborent pour créer une structure de classes bien organisée, modulaire et maintenable.
- Enfin, les liens entre les classes sont représentés par des attributs qui contiennent des références vers d’autres classes. L’héritage s’applique également à ces liens, permettant aux classes filles d’hériter des liens vers d’autres classes et d’interagir avec elles en respectant les niveaux de visibilité définis par l’encapsulation.