Héritage

La principale caractéristique de la POO est d'encourager la réutilisation de code au travers de l'héritage. Une nouvelle classe est créée à partir d'une classe existante, appelée classe de base. La classe dérivée utilise les membres de la classe de base, mais peut également les modifier ou en ajouter.

De nombreux types sont des variations de types existants. Il est souvent fastidieux de développer du nouveau code pour chacun d'entre eux. De plus, du nouveau code signifie aussi de nouvelles erreurs. La classe dérivé hérite de la description de la classe de base, donc tout re-développement et re-test du code n'est pas nécessaire. Les relations d'héritage sont hiérarchiques.

La hiérarchie est la méthode qui permet de copier les éléments dans leur diversité et dans leur complexité. Elle introduit la classification des objets. Par exemple, la table périodique des éléments contient des gza. Ils possèdent les propriétés inhérente à tout élément périodique.

Les gaz inertes constituent la sous-classe importante suivante. La hiérarchie est qu'un gaz inerte, tel qu'argon, est un gaz, et un gaz à son tour est une partie du système. Une telle hériarchie permet d'interpréter le comportement des gaz inertes facilement. Nous savons que leurs atomes contiennent des protons et des électrons, ce qui est vrai pour tous les autres éléments.

Nous savons qu'il sont à l'état gazeux à température ambiante, comme tous les gaz. Nous savons qu'aucun gaz de la sous-classe des gaz inertes, n'entre en réaction chimique avec d'autres éléments, et c'est une propriété de tous les gaz inertes.

Considérons l'exemple de l'héritage des formes géométriques. Pour décrire toute la variété des formes simples (cercle, triangle, rectangle, carré, etc.), la meilleure façon est de créer une classe de base (ADT), qui est l'ancêtre de toutes les classes dérivées.

Créons une classe de base CShape, qui ne contient que les membres les plus communs décrivant une forme. Ces membres décrivent les propriétés qui sont caractéristiques de n'importe quelle forme - le type de la forme et les coordonnées de son principal point d'ancrage.

Exemple :

//--- La classe de base Shape
class CShape
  {
protected:
   int       m_type;                   // Type de la forme
   int       m_xpos;                   // X - coordonnées du point de base
   int       m_ypos;                   // Y - coordonnées du point de base
public:
             CShape(){m_type=0; m_xpos=0; m_ypos=0;} // constructeur
   void      SetXPos(int x){m_xpos=x;} // assigne X
   void      SetYPos(int y){m_ypos=y;} // assigne Y
  };

Ensuite, créons de nouvelles classes dérivées de la classe de base dans laquelle nous ajouterons les champs nécessaires, chacun spécifiant une certaine classe. Pour la forme Cercle, il est nécessaire d'ajouter un membre qui contient la valeur du rayon. La forme Carré est caractérisée par la valeur du côté. Donc, les classes dérivées, héritées de la classe de base CShape seront déclarées comme suit :

//--- La classe dérivée cercle
class CCircle : public CShape        // Après le symbole ':', nous spécifions la classe de base
  {                                    // à partir de laquelle l'héritage est fait
private:
   int             m_radius;           // rayon du cercle
 
public:
                   CCircle(){m_type=1;}// constructeur, type 1 
  };

Pour la forme Carré, la déclaration de la classe est similaire :

//--- la classe dérivée Carré
class CSquare : public CShape          // Après le symbole ':', nous spécifions la classe de base
  {                                    // à partir de laquelle l'héritage est fait
private:
   int            m_square_side;       // côté du carré
 
public:
                  CSquare(){m_type=2;} // constructeur, type 2 
  };

Il est à noter qu'au moment de la création de l'objet, la constructeur de la classe de base est appelé en premier, et le constructeur de la classe dérivée est ensuite appelée. Lorsqu'un objet est détruit, le destructeur de la classe dérivée est appelé en premier, et ensuite le destructeur de la classe de base est appelé.

En déclarant les membres les plus généraux dans la classe de base, nous pouvons donc ajouter des membres supplémentaires dans les classes dérivées pour spécifier des classes particulières. L'héritage permet de créer de puissantes bibliothèques de code qui peuvent être réutilisées plusieurs fois.

La syntaxe pour créer une classe dérivée à partir d'une classe existante est de la forme suivante :

class class_name : 
          (public | protected | privateopt  base_class_name
  {                                    
   déclaration des membres de la classe
  };

 

L'un des aspects de la classe dérivée est la visibilité (ouverture) de ses membres successeurs (héritiers). Les mots-clés public, protected et private sont utilisés pour indiquer la mesure dans laquelle les membres de la classe de base seront disponibles pour les classes dérivées. le mot-clé public après le symbole ':' dans l'en-tête de la classe dérivée indique que les membres protected et public de la classe de base CShape doivent être hérités comme membres protected et public members dans la classe dérivée CCircle.

Les membres privés de la classe de base ne sont pas disponibles pour la classe dérivée. L'héritage public signifie égaement que les classes dérivées (CCircle et CSquare) sont des CShapes. Le Carré (CSquare) est une forme (CShape), mais la forme n'est pas forcément un carré.

La classe dérivée est une modification de la classe de base, elle hérite des membres protected et public de la classe de base. Les constructeurs et destructeurs de la classe de base ne peuvent pas être hérités. En plus des membres de la classe de base, de nouveaux membres sont ajoutés dans une classe dérivée.

La classe dérivée peut inclure l'implémentation des fonctions membres, différente de la classe de base. Cela n'a rien à voir avec une surcharge, lorsque la signification du même nom de fonction peut être différente pour différentes signatures.

Dans l'héritage protégé, les membres public et protected de la classe de base deviennent des membres protected de la classe dérivée. Dans l'héritage privé, les membres public et protected de la classe de base deviennent des membres privés de la classe dérivée.

Dans l'héritage protégé et privé, la relation comme quoi "l'objet d'une classe dérivée est un objet de la classe de base" n'est pas vraie. Les types d'héritage protégé et privé sont rares, et chacun d'eux doit être utilisé avec précaution.

Il faut comprendre que le type de l'héritage (public, protected ou private) n'affecte pas la façon d'accéder aux membres des classes de base dans la hiérarchie d'héritage depuis une classe dérivée. Avec n'importe quel type d'héritage, seuls les membres de la classe de base déclarés avec les spécificateurs public et protected seront disponibles en dehors des classes dérivées. Considérons cela dans l'exemple suivant :

#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Classe d'exemple avec plusieurs types d'accès                    |
//+------------------------------------------------------------------+
class CBaseClass
  {
private:             //--- Le membre privé n'est pas disponible depuis les classes dérivées
   int               m_member;
protected:           //--- La méthode protected est disponible depuis la classe de base et ses classes dérivées
   int               Member(){return(m_member);}
public:              //--- Le constructeur de la classe est disponible pour tous les membres de la classe
                     CBaseClass(){m_member=5;return;};
private:             //--- Une méthode private pour affecter une valeur à m_member
   void              Member(int value) { m_member=value;};
 
  };
//+------------------------------------------------------------------+
//| Classe dérivée avec des erreurs                                  |
//+------------------------------------------------------------------+
class CDerived: public CBaseClass // la spécification d'héritage public peut être manquante puisque c'est la valeur par défaut
  {
public:
   void Func() // Dans la classe dérivée, définit une fonction avec des appels aux membres de la classe de base
     {
      //--- Tentative de modifier un membre privé de la classe de base
      m_member=0;        // Erreur, le membre privé de la classe de base n'est pas disponible
      Member(0);         // Erreur, la méthode privée de la classe de base n'est pas disponible dans les classes dérivées
      //--- Lecture du membre de la classe de base
      Print(m_member);   // Erreur, le membre privé de la classe de base n'est pas disponible
      Print(Member());   // Aucune erreur, la méthode protected est disponible depuis la classe de base et ses classes dérivées
     }
  };

Dans l'exemple ci-dessu, CBaseClass n'a qu'une seule méthode publique — le constructeur. Les constructeurs sont appelés automatiquement lors de la création d'un objet de classe. Le membre privé m_member et la méthode protected Member() ne peuvent donc pas être appelés depuis l'extérieur. Mais dans le cas d'un héritage public, la méthode Member() de la classe de base sera disponible pour les classes dérivées.

Dans le case d'héritage protégé, tous les membres de la classe de base avec un accès public et protected deviennent protected. Cela signifie que si des membres et méthodes publics de la classe de base étaient accessibles depuis l'extérieur, avec l'héritage protected, ils ne sont disponibles que depuis les classes de la classe dérivée et ses propres classes dérivées.

//+------------------------------------------------------------------+
//| Classe d'exemple avec plusieurs types d'accès                    |
//+------------------------------------------------------------------+
class CBaseMathClass
  {
private:             //--- Le membre privé n'est pas disponible depuis les classes dérivées
   double            m_Pi;
public:              //--- Définir et récupérer une valeur pour m_Pi
   void              SetPI(double v){m_Pi=v;return;};
   double            GetPI(){return m_Pi;};
public:              // Le constructeur de classe est disponible pour tous les membres
                     CBaseMathClass() {SetPI(3.14);  PrintFormat("%s",__FUNCTION__);};
  };
//+------------------------------------------------------------------+
//| Classe dérivée dans laquelle m_Pi ne peut pas être modifié       |
//+------------------------------------------------------------------+
class CProtectedChildClass: protected CBaseMathClass // Héritage protégé
  {
private:
   double            m_radius;
public:              //--- Méthodes publiques de la classe dérivée
   void              SetRadius(double r){m_radius=r; return;};
   double            GetCircleLength(){return GetPI()*m_radius;};
  };
//+------------------------------------------------------------------+
//| Fonction de lancement du programme                               |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Lors de la création d'une classe dérivée, le constructeur de la classe de base sera appelé automatiquement
   CProtectedChildClass pt;
//--- Spécifie le rayon
   pt.SetRadius(10);
   PrintFormat("Longueur=%G",pt.GetCircleLength());
//--- Si nous décommentons la ligne ci-dessous, nous obtiendrons une erreur au moment de la compilation, puisque SetPI() est maintenant protected
// pt.SetPI(3); 
 
//--- Déclarons maintenant une variable de la classe de base et essayon de définir la constante Pi égale à 10
   CBaseMathClass bc;
   bc.SetPI(10);
//--- Voici le résultat
   PrintFormat("bc.GetPI()=%G",bc.GetPI());
  }

L'exemple montre que les méthodes SetPI() et GetPi() de la classe de base CBaseMathClass sont ouvertes et disponibles pour être appelées depuis n'importe où dans le programme. Mais en même temps, pour CProtectedChildClass qui est dérivée, ces méthodes ne peuvent être appelées que depuis les méthodes de la classe CProtectedChildClass ou de ses classes dérivées.

Dans le case d'héritage privé, tous les membres de la classe de base avec un accès public et protected deviennent private, et il devient impossible de les appeler par la suite.

MQL5 ne supporte pas l'héritage multiple.

Voir aussi

Structures et Classes