La notion d'héritage a déjà été abordée dans le cours I sur la programmation objet, mais simplement du point de vue de l'utilisation.
Dans cette partie du cours, nous allons voir comment définir explicitement une relation d'héritage entre deux classes, puis comment exploiter de la meilleure manière cette relation par la redéfinition de méthode et le polymorphisme.
L'exemple de projet qui nous servira à introduire ces notions se trouve dans le dossier Exemple-ProgObjet2/Formes.
Il contient la définition d'une classe Cercle comme sous-classe d'une autre classe nommée Forme.
Toute forme possède une aire. Un cercle est une forme spéciale qui possède également un rayon.
Voici, l'interface graphique de ce projet:
Le bouton Nouvelle Forme permet de générer une forme d'aire aléatoire.
Le bouton Nouveau Cercle permet de générer un cercle de rayon aléatoire.
Chaque forme générée (cercle ou forme quelconque) est stockée dans un même tableau TF dont les éléments sont de type Forme. Le nombre de formes générées est stockée dans un entier NF. TF et NF sont des variables globales dont voici la déclaration.
Notez qu'il est impossible en principe de stocker dans un même tableau des données de types différents. Nous verrons un peu plus loin ce qui permet ce miracle.
Le bouton Afficher permet d'afficher les attributs d'une forme donnée en donnant son numéro (l'indice de l'élément du tableau dans lequel elle est rangée).
La figure précédente montre l'affichage de la forme numéro 3: c'est une instance de la classe Forme dont l'aire est égale à 71.
Voici l'affichage de la forme numéro 2:
Il s'agit cette fois-ci d'un cercle.
Voici les interfaces des deux classes.
La classe Forme possède deux méthodes:
La classe Cercle est définie comme une sous classe de la classe Forme. Elle possède trois méthodes:
On voit ici apparaitre le principe de déclaration d'une relation d'héritage entre deux classes. Pour déclarer une classe C1 en tant que sous classe d'une classe C2, on déclare C1 de la manière suivante:
C1 = class (C2) . . end;
Voici l'implementation de la classe Forme.
Dans le corps la méthode AjouterForme apparait un mot clé self.
Ce mot clé peut être utilisé dans toute méthode pour désigner l'instance à laquelle la méthode s'applique. Il représente plus précisement son adresse.
Ici self représente l'adresse de la forme à laquelle on applique la méthode AjouterForme.
L'affectation TF[NF]:=Self, ajoute donc la forme (en fait son adresse) dans le tableau TF, à la suite des formes déjà mémorisées.
Dans la méthode AfficherAttributs figure l'appel de la méthode ClassName. Cette méthode retourne le nom de la classe à laquelle appartient un objet quelconque. C'est en fait une méthode de la classe TObject dont, rappelons le, toute classe Pascal est une sous-classe. Elle peut donc être appliquée à une instance de la classe Forme.
Voici l'implémentation de la classe cercle.
L'affichage des attributs d'un cercle est assez similaire à l'affichage des attributs d'une forme. Il suffit de rajouter l'affichage du rayon.
Pour éviter d'écrire deux fois le même code, nous avons utilisé ici la redéfinition de méthode.
Dans le corps de la procédure AfficherAttributs figure l'instruction inherited AfficherAttributs. Le mot clé inherited permet de préciser qu'il s'agit de la méthode AfficherAttributs de la classe mère.
En appelant cette méthode, on obtient donc l'affichage de l'aire du cercle et de sa classe.
Voici les procédures évènementielles associées aux boutons de l'interface graphique.
L'affichage d'une forme donnée est réalisée par la procédure Bt_AfficherClick.
Elle commence par lire le numéro (num) de la forme à afficher, c'est à dire l'indice de l'élément du tableau TF dans lequel cette forme est stockée.
TF[num] contient donc un objet de la classe Forme ou un objet de la classe Cercle.
Pour distinguer les deux cas, nous avons utilisé l'opérateur is qui permet de savoir si un objet appartient à une classe donnée.
Attention: si un objet appartient à une classe, il appartient également à sa classe mère !
Si TF[num] est une instance de la classe Cercle, nous ne pouvons pas appliquer directement la méthode AfficherAttributs pour l'afficher.
En effet, bien que TF[num] contienne un objet de la classe Cercle, il est considéré par le compilateur comme un objet de la classe Forme car le tableau TF a été déclaré comme un tableau de Forme.
Pour forcer le compilateur à interpréter TF[num] comme un objet de la classe Cercle, nous avons utiliser le polymorphisme, qui se traduit en Pascal par l'opérateur as.
TF[num] as Cercle signifie que le compilateur doit interprèter TF[num] comme un cercle. De ce fait, la méthode AfficherAttributs appliquée à cette objet sera bien celle de la classe Cercle.
La génération aléatoire d'un cercle et son stockage dans le tableau TF est réalisée par la procédure Bt_NouveauCercleClick.
Elle illustre un autre aspect du polymorphisme. En effet, lorsque l'on applique la méthode AjouterForme au cercle généré, cela provoque (voir le code de la méthode AjouterForme) son stockage dans un élément du tableau TF.
Or ces éléments sont à priori tous des objets de la classe Forme !
En fait cela ne pose pas de problème au compilateur car TF ne contient que des pointeurs.
L'instruction TF[NF]:=self ne fait rien d'autre que stocker l'adresse de l'objet dans le tableau TF.