XII. Objets▲
Nous abordons dans ce chapitre l'une des notions les plus importantes de la
programmation moderne. En effet, la Programmation Orientée Objets, mieux connue sous
l'acronyme de P.O.O, est très largement répandue et utilisée actuellement. Cette
programmation , ou plutôt ce style de programmation, s'appuie sur le concept
d'objets. Pascal Objet doit d'une part son nom au langage Pascal d'origine, et
d'autre part aux grandes possibilités offertes par ce langage dans le domaine des
objets depuis la première version de Delphi (et à moindre mesure dans les dernières
versions de Turbo Pascal).
Ce chapitre présente la notion d'objet sans rentrer dans trop de détails techniques
reservés à un futur chapitre et explique comment utiliser les objets. La notion de
classe est également vue en détail, ainsi que la notion de hiérarchie de classes. Le
rapport entre composants et objets sera également expliqué en détail.
Vous trouverez peut-être ce chapitre plus difficile que ce qui précède : c'est
normal. La notion d'objet n'est pas simple à comprendre, mais une fois que l'on a
compris le « truc », tout s'illumine. J'espère que ce sera le cas pour vous et je
m'engage à tout faire pour faciliter cela.
XII-A. Définitions des notions d'objet et de classe▲
La notion d'objet est, à mon sens, une des notions les plus passionnantes de la
programmation. Bien qu'apparament très complexe et étendue, la notion d'objet répond
à des besoins très concrets et rend possible l'existence de systèmes évolués tels
que les environnements graphiques comme Microsoft Windows. Sans cette notion,
programmer de tels systèmes relèverait sinon de l'utopie, au moins de l'enfer.
Le langage Pascal Objet doit bien sûr la deuxième partie de son nom aux grandes
possibilités de ce langage dans l'exploitation des objets. La notion d'objet est
basée sur un besoin simple : celui de regrouper dans une même structure des
données et du code (des instructions en langage Pascal Objet dans notre cas)
permettant de manipuler ces données. Les objets répondent à ce besoin, en apportant
une foule de possibilités très puissantes qui en font un passage presque obligé dans
la majorité des gros projets développés sous Delphi. Nous ne verrons ici qu'une
petite partie de ces possibilités, l'essentiel étant réservé au chapitre sur la
création d'objets.
Avant d'en venir aux définitions, il faut savoir que le concept d'objet est
indépendant des langages qui permettent d'utiliser les objets. Nombre de langages
plus ou moins modernes supportent les objets, mais chacun supporte un certain nombre
de fonctionnalités liées aux objets. Les possibilités offertes par Pascal Objet sont
très correctes, mais toutefois moins étendues que celles offertes par le langage C++
par exemple.
XII-A-1. Objet▲
Concrètement, un objet est une donnée possédant une structure complexe. On
utilisera les objets en utilisant des variables Pascal Objet, comme cela se fait
déjà avec les autres données classiques telles les chaînes de caractères ou les
pointeurs. Ces variables contiennent, puisque c'est leur principe de base, des
données et du code Pascal Objet permettant de les traiter. C'est une des nouveautés
marquantes des objets : on peut y stocker des données mais aussi des instructions
Pascal Objet (nous verrons cela en détail dans le chapitre sur la création
d'objets). Les données consistent en des variables de n'importe type (y compris des
objets). Le code Pascal Objet est réparti dans des procédures et fonctions nommées
méthodes (c'est leur dénomination dans le monde des objets). Si ce terme de
méthode vous est déjà famillier, ce n'est pas dû au hasard : les composants,
auxquels se rattachait jusqu'ici le concept de méthodes, sont en effet des
objets.
Delphi propose un large éventail d'objets tout faits, que nous allons apprendre à
utiliser dans ce chapitre. Cette collection livrée avec Delphi s'appelle la VCL
(Visual Component Library : Bibliothèque de composants visuels). Cette VCL fait de
Delphi un instrument unique car aucun autre outil de programmation visuelle ne
propose une telle bibliothèque prête à l'emploi et aussi facile à utiliser. La
création d'objets est également possible mais constitue un objectif plus ambitieux
et plus complexe que nous aborderons dans un prochain chapitre.
Les méthodes et variables contenues dans les objets sont complétés par d'autres
éléments. Parmi ces éléments particuliers, figurent les propriétés (que vous
connaissez bien maintenant, n'est-ce pas ?) et deux méthodes aux dénominations un
peu barbares puisqu'on les appelle constructeur et destructeur.
XII-A-2. Classe▲
Déjà cité deux ou trois fois dans ce chapitre, ce terme n'a pas encore fait l'objet d'une explication. Nous venons de voir que les objets sont des variables. Les classes sont les types permettant de déclarer ces variables. Là où auparavant une variable était d'un type donné, un objet sera dit d'une classe donnée. Venons-en tout de suite à un exemple :
var
S: string
;
Button1: TButton;
Dans l'exemple ci-dessus, deux variables sont déclarées. Leur noms sont « S » et « Button1 ». Les types de ces deux variables sont respectivement « string » et « TButton ». Vous connaissez bien le type « string » pour l'avoir utilisé de nombreuses fois depuis le début de ce guide. Le type « TButton » ne vous est pas non plus inconnu puisqu'il figure dans le code source de vos applications depuis un certain temps déjà. Si vous ne voyez pas où, créez une nouvelle application, placez un bouton sur la seule fiche du projet, et allez regarder le source de l'unité associée à la fiche : vous devriez pouvoir trouver un extrait de code similaire à celui présenté ci-dessous :
...
type
TForm1 = class
(TForm)
Button1: TButton;
...
le type TButton correspond en fait au composant Button décrit utilisé dés les
premiers chapitres de ce guide. Le type « TButton » est une classe, c'est-à-dire
que la variable « Button1 » de type « TButton » est un objet. Comme vous pouvez le
constater, déclarer un objet se fait de la même manière que pour une variable, à
ceci près qu'on le déclare en utilisant une classe et non un type classique.
Une classe, un peu comme le fait un type pour une variable, détermine entièrement
la structure des objets de cette classe. Plus précisément, c'est la classe qui
définit les méthodes et les données (mais pas leurs valeurs) contenues dans les
futurs objets de cette classe. Pour reprendre un hyper-classique (peut-être pas
pour vous, désolé), une classe peut être comparée à un moule à gâteaux. Les objets,
quant à eux, je vous le donne en mille, sont les gâteaux qu'on peut réaliser à
partir de ce moule. En admettant que le moule définisse entièrement la forme du
gâteau, c'est la même chose pour les classes : elles déterminent entièrement la
structure des objets de cette classe (de ce type, donc). Par contre, lorsque vous
avez un moule à tarte par exemple, qui peut donc être assimilé à une classe
précise, vous pouvez faire une tarte aux pommes ou aux poires, ce n'est pas
contrôlé par le moule. Il en va de même pour la classe en ce qui concerne les
données : elle définit les noms et les types des données mais pas leurs valeurs.
Chaque objet a ses données dont les noms et types sont déterminées par
la classe, mais dont les valeurs sont indépendantes pour chaque objet. Ainsi, la
classe « TMouleATarte » définirait une variable « Fruit » de type énuméré «
TSorteDeFruit » par exemple. Chaque objet de classe « TMouleATarte » pourrait alors
spécifier une valeur pour « Fruit ». Cette valeur serait indépendante des autres
objets et de la classe.
Et ceci termine les définitions. Dans le paragraphe suivant, vous allez apprendre à
utiliser les objets et les classes.
XII-B. Utilisation des objets▲
Les objets ne s'utilisent pas exactement comme les autres variables. Comme les pointeurs, ils nécessitent des opérations spécifiques et leur utilisation se fait en respectant certaines règles qui ne doivent rien au hasard. Ce paragraphe est dédié à l'utilisation et à la manipulation des objets.
XII-B-1. Construction et destruction▲
Comme les pointeurs, les objets nécessitent deux étapes particulières avant et
après leur utilisation. Un objet se construit, s'utilise puis se détruit. La
construction se fait par une instruction particulière que nous verrons ci-dessous.
La destruction se fait également par une simple instruction qui fait appel au
destructeur.
Afin d'illustrer ces propos quelque peu abstraits, nous allons étudier une classe
proposée par Delphi : la classe TStringList. Son but est de manipuler une liste de
chaînes de caractères. Vous allez vous familiariser avec la manipulation des objets
en apprenant à vous servir d'un objet de cette classe.
Commençons donc par déclarer un objet de classe TStringList. Nous utiliserons la
désormais classique procédure associée à l'événement OnClick d'un bouton.
var
Lst: TStringList;
L'exemple-ci-dessus déclare un objet Lst de type TStringList. La construction d'un
objet se fait par une instruction dont la syntaxe est la suivante :
objet := classe.constructeur[(parametres)]
objet désigne l'objet que vous voulez construire. classe est la classe de
l'objet à construire et donc qui a servi à déclarer la variable qui représente l'objet.
constructeur est le nom du constructeur défini par la classe de l'objet. Le plus souvent,
ce constructeur s'appelle « create ». Cette méthode, comme toutes les procédures, peut
éventuellement avoir des paramètres. Ceux-ci sont le cas échéant et comme d'habitude
transmis entre parenthèses et sont indiqués ci-dessus entre crochets pour indiquer qu'ils
sont parfois absents. Vous remarquerez que l'appel du constructeur se fait comme l'appel
d'une méthode pour les composants. En fait, le constructeur est une méthode et les composants
sont des objets, ce qui explique cette similarité. Vous remarquerez également que l'instruction
est une affectation. Cette écriture permet certaines constructions intéressantes que nous
étudierons plus tard. La raison de cette construction est qu'appeler une méthode (même un
constructeur) d'un objet non déjà construit est absurde : on utilise la classe de l'objet
en lieu et place de l'objet (ceci sera expliqué en détail dans le chapitre consacré à la
création d'objets).
Comme la construction et la destruction vont systématiquement de paire, la
destruction se fait par une instruction simple qui a la forme suivante :
objet.destructeur[(parametres)]
objet désigne l'objet que l'on souhaite détruire. Il ne sera plus possible d'utiliser l'objet après l'instruction, à moins de le reconstruire. destructeur est le nom du destructeur défini par la classe de l'objet. Il est assez rare qu'un destructeur ait besoin de paramètres, si c'est le cas, il suffit de les transmettre entre parenthèses.
Voici maintenant la construction et la destruction en pratique :
procedure
TForm1.Button1Click(Sender: TObject);
var
Lst: TStringList;
begin
Lst := TStringList.Create;
Lst.Destroy;
end
;
La procédure ci-dessus construit l'objet Lst. Examinez bien l'instruction : « create » est le constructeur de la classe TStringList. Il n'a pas de paramètres. Au-delà de cette construction, l'objet est utilisable. Nous allons voir l'utilisation dans un prochain paragraphe. La destruction est effectuée pour cette fois immédiatement après la construction. Vous constaterez que la destruction, contrairement à la construction qui prend une forme spéciale, est un simple appel à la méthode spéciale qu'est le destructeur.
XII-B-2. Manipulation des objets▲
Il va y avoir un air de déjà vu ici si vous avez suivi le chapitre sur la
manipulation des composants. En effet, tous les composants étant des objets, ils se
manipulent comme les objets (et non le contraire, comme on serait tenté de le
croire, car il y a des objets qui ne sont pas des composants, comme les objets de
classe TStringList). Ce qui va être dit ici est donc une généralisation de ce qui a
été dit pour les composants et non pas une simple répétition.
Nous avons vu qu'un objet contient des variables, des méthodes, des propriétés et
deux méthodes spéciales que sont le constructeur et le destructeur. Parmi tous ces
éléments, seuls certains sont accessibles. Cette notion d'accessibilité sera
expliquée au paragraphe sur les sections publiques et privées d'un objet.
Parmi les éléments accessibles figurent rarement les variables : elles sont souvent
le domaine réservé de l'objet et il n'est nul besoin d'y avoir accès. Par contre,
un certain nombre de méthodes et de propriétés sont accessibles. Le constructeur et
le destructeur sont bien évidemment toujours accessibles. Pour faire appel à une
méthode d'un objet, il suffit d'employer la syntaxe habituelle :
objet.methode[(parametres)]
objet désigne l'objet dont on veut appeler une méthode. methode est la méthode qu'on
veut appeler. Si cette méthode a des paramètres, il suffit de les transmettre entre parenthèses.
Etant donné que les méthodes regroupent des procédures et des fonctions, dans le cas de ces dernières,
le résultat est utilisable comme une valeur du type du résultat de la fonction. Par exemple, dans une
affectation, cela donne :
variable := objet.fonction_methode[(parametres)]
La plupart des objets possèdent également des propriétés. Leur utilisation se fait
comme pour un composant. Selon ce que la propriété est en lecture seule ou en
lecture/écriture, elle s'utilise comme une constante ou une variable du type de la
propriété. Certaines propriétés particulières comme les propriétés tableau ou objet
sont également au programme. Les premières, vous les connaissez déjà. Les deuxièmes
sont simplement des propriétés de type objet, c'est-à-dire d'une classe
déterminée.
Poursuivons l'exemple débuté au paragraphe précédent. Nous allons ajouter une
chaîne à la liste. L'ajout d'une chaîne se fait en appelant la méthode "Add" de
l'objet liste de chaînes. Cette méthode est une procédure qui accepte en unique
paramètre la chaîne à ajouter à la liste. Voici ce que cela donne en pratique :
procedure
TForm1.Button1Click(Sender: TObject);
var
Lst: TStringList;
begin
Lst := TStringList.Create;
Lst.Add('Bonjour !'
);
Lst.Destroy;
end
;
Vous remarquerez que l'objet est construit, puis utilisé, et enfin détruit : c'est
ainsi qu'il faudra toujours faire, même si ces trois étapes auront tendance à
s'espacer avec le temps et l'expérience. Vous noterez également que nous ne nous
soucions pas d'effacer la chaîne de la liste, car c'est une tâche effectuée
automatiquement lors de la destruction de l'objet (c'est le destructeur qui fait ce
travail pour nous). L'instruction qui ajoute une chaîne à la liste est très facile
à décortiquer : on appelle la méthode Add de l'objet Lst avec le paramètre 'bonjour !'.
La classe TStringList possède une propriété en lecture seule "Count" (de type
integer) indiquant le nombre de chaînes actuellement dans la liste. Nous allons
afficher deux messages indiquant cette valeur, avant et après l'ajout de la chaîne.
Le premier message devrait indiquer 0 et le second 1.
procedure
TForm1.Button1Click(Sender: TObject);
var
Lst: TStringList;
begin
Lst := TStringList.Create;
ShowMessage(IntToStr(Lst.Count));
Lst.Add('Bonjour !'
);
ShowMessage(IntToStr(Lst.Count));
Lst.Destroy;
end
;
La propriété Count est utilisé comme une constante puisqu'elle est en lecture
seule. On utilise la fonction IntToStr pour transformer sa valeur en chaîne de
caractère affichée par ShowMessage. Le premier appel se situe juste après la
construction de l'objet : à ce moment, aucune chaîne n'est encore présente dans la
liste. Le nombre de chaînes est donc 0 et c'est bien ce qu'affiche le message.
Après l'ajout de la chaîne, la même opération est effectuée. Comme une chaîne a été
ajoutée, l'objet a « mis à jour » sa propriété Count et la valeur renvoyée est non
plus 0 mais 1.
C'est très intéressant d'ajouter des chaînes à une liste, mais il faut également
pouvoir y accèder. Ceci se fait par une propriété tableau, qui a l'immense avantage
d'être par défaut, ce qui vous dispense de retenir son nom : Strings. Cette
propriété tableau a ses indices numérotés à partir de 0. La première chaîne de la
liste, lorsqu'elle existe, est donc :
Lst.String
[0
]
ce que l'on peut écrire plus simplement (et cette deuxième écriture, équivalente à
la première, lui sera désormais systématiquement préférée, car un bon développeur
est comme un matheux : partisan du moindre effort) :
Lst[0
]<br/>
Vous admettrez que c'est plus court et plus facile à retenir, bien que moins explicite. Chacune de ces deux expressions est de type String. Modifions donc notre petit programme pour qu'il affiche la chaîne que nous venons d'ajouter dans la liste :
procedure
TForm1.Button1Click(Sender: TObject);
var
Lst: TStringList;
begin
Lst := TStringList.Create;
Lst.Add('Bonjour !'
);
ShowMessage(Lst[0
]);
Lst.Destroy;
end
;
Lst[0], qui est de type String, est affiché par ShowMessage, ce qui permet à votre
ordinateur d'être poli pour la première fois de la journée.
Dans les listes d'éléments telles que les objets de classe TStringList en
manipulent, les éléments sont souvent acccessible via une propriété tableau indexée
à partir de 0. Une propriété, souvent nommée Count, permet de connaître le nombre
d'éléments dans la liste. Les éléments sont donc, sous réserve qu'il y en ait dans
la liste, indexés de 0 à Count - 1. Si je prends le temps de bien indiquer cela,
c'est parce qu'il est souvent nécessaire de passer en revue tous les éléments via
une boucle for. Les bornes inférieures et supérieures à utiliser sont dans ce cas 0
et Count - 1. Voici une nouvelle version de la procédure qui utilise cela pour
afficher toutes les chaînes ajoutées à la liste.
procedure
TForm1.Button1Click(Sender: TObject);
var
Lst: TStringList;
indx: integer
;
begin
Lst := TStringList.Create;
Lst.Add('Bonjour !'
);
Lst.Add('Maître'
);
if
Lst.Count > 0
then
for
indx := 0
to
Lst.Count - 1
do
ShowMessage(Lst[indx]);
Lst.Destroy;
end
;
L'exemple ci-dessus ajoute deux chaînes à la liste. Le suspense sur la valeur de Count n'est pas vraiment très intense, mais essayez d'oublier que Count a une valeur connue dans cet exemple particulier : pensez au cas général où Count est une valeur inconnue. La boucle for affiche toutes les chaînes présentes dans la liste, quel que soit leur nombre. indx parcourant les valeurs entre 0 et Count - 1, Lst[indx] parcours donc toutes les chaînes de la liste. Le bloc for est inclus dans un bloc if qui permet de n'effectuer l'affichage que si la propriété Count vaut au moins 1, c'est-à-dire que l'affichage n'est lancé que s'il y a quelque chose à afficher.
XII-B-2-a. Exercice résolu▲
Passons à la vitesse supérieure. Nous allons utiliser une fonctionnalité fort
appréciable de la classe TStringList : la méthode Sort. Cette méthode applique un
tri alphabétique sur les chaînes présentes dans la liste maintenue par l'objet
dont on appelle la méthode Sort. Concrètement, les chaînes sont simplement
réorganisées de sorte que la première dans l'ordre alphabétique ait l'indice 0
dans la propriété tableau par défaut de l'objet. Pour illustrer cela, nous allons
confectionner une procédure (toujours déclenchée par un clic sur un bouton, pour
faire dans l'originalité) qui demande de saisir des chaînes. La procédure
demandera des chaînes jusqu'à ce que l'on clique sur "Annuler" et non "OK". La
procédure affichera alors le nombre de chaînes entrées dans la liste, puis les
chaînes dans l'ordre alphabétique.
Si vous voulez réaliser cela comme un exercice, c'est le moment d'arrèter de lire
et de vous mettre à chercher : ce qui suit réalise pas à pas ce qui vient d'être
proposé ci-dessus.
La première chose à faire est de bien discerner les étapes à programmer :
- initialisation de l'objet qui gérera la liste
- une boucle demandant un nombre à priori inconnu de chaînes
- l'affichage du nombre de chaînes
- le tri alphabétique de la liste
- l'affichage d'un nombre connu de chaînes
- destruction de l'objet
La première étape nécessite une boucle : chaque itération lira une chaîne et la
boucle s'arrètera lorsque la condition "la chaine entrée est la chaîne vide" est
réalisée. Le problème est qu'une boucle for est inadaptée puisque le nombre
de chaînes que l'utilisateur désire rentrer est inconnu et puisque nous ne
souhaitons pas fixer ce nombre. Restent deux boucles possibles : une boucle
while ou une boucle repeat. Pour savoir laquelle utiliser, la
question est toujours la même : le contenu de la boucle sera-t-il à exécuter au
moins une fois ? La réponse dépend de ce que nous allons effectuer à chaque
itération, c'est pour cela qu'il est toujours nécessaire de réfléchir à un
programme d'abord sur papier avant de se lancer dans l'écriture du code.
Nous allons utiliser la fonction InputQuery pour lire les chaînes à rentrer dans
la liste. Cette fonction renvoie un résultat booléen qui peut être utilisé
indifférement dans une condition d'arrèt ou de poursuite (selon le type de boucle
choisie). Etant donné que la fonction renvoie true si l'utilisateur valide sa
saisie, on peut utiliser la boucle suivante :
while
InputQuery({...}
) do
{...}
Lors de l'entrée dans la boucle, InputQuery sera exécutée. Son résultat sera utilisé comme condition d'arrèt : si l'utilisateur annule, on n'effectue pas d'itération, s'il valide, on effectue une itération et on refait une demande de chaîne, et ainsi de suite. Nous aurions pu écrire les choses de façon plus compréhensible mais plus longue en utilisant une variable booléenne :
Cont := True
;
while
Cont do
begin
Cont := InputQuery({...}
);
...
end
;
ou même comme cela :
repeat
Cont := InputQuery({...}
);
...
until
not
Cont;
mais ces deux méthodes auraient introduit des difficultés supplémentaires car il
aurait fallu tester la valeur de Cont avant d'effectuer un quelconque traitement,
ce qui nous aurait obligé à utiliser une boucle if. La méthode choisie est
plus propre et est plus proche d'un raisonnement logique du genre "tant que
l'utilisateur valide sa saisie, on traite sa saisie".
Passons au contenu de la boucle, c'est-à-dire à ce qui est éxécuté à chaque
itération : La chaîne entrée par l'utilisateur est tout simplement ajoutée à la
liste. Il faudra également penser à vider la chaîne lue car rappelez-vous que
cette chaîne est affichée dans la boite de dialogue de saisie. Il faudra aussi la
vider avant la première itération car la première lecture s'effectuera avant le
premier « vidage ». Voici donc la première partie de la procédure, qui inclue
également la construction et la destruction de l'objet de classe TStringList
puisque ce dernier est utilisé dans la boucle while :
procedure
TForm1.Button1Click(Sender: TObject);
var
StrLst: TStringList;
STemp: String
;
begin
StrLst := TStringList.Create;
STemp := ''
;
while
InputQuery('Entrez une chaîne'
, 'Chaine à ajouter à la liste :'
, STemp) do
begin
StrLst.Add(STemp);
STemp := ''
;
end
;
StrLst.destroy;
end
;
Ce code permet de lire autant de chaînes que l'on veut et de les stocker dans une liste de chaînes. La deuxième étape qui consiste à afficher le nombre de chaînes entrées par l'utilisateur est la plus simple de ce que nous avons à faire :
ShowMessage('Vous avez entré '
+ IntToStr(StrLst.Count) + ' chaînes'
);
Il nous faut ensuite trier la liste. Pour cela, il suffit d'appeler la méthode Sort de l'objet StrLst :
StrLst.Sort;
Enfin, il nous faut afficher les chaînes. Ici, on ne sait pas combien il y en a, mais ce nombre est contenu dans la propriété Count de l'objet. On utilise donc une boucle for avec une variable allant de 0 à StrLst.Count - 1. L'affichage passe par un simple appel à ShowMessage. Pour donner du piquant à cet affichage, on donne à l'utilisateur un affichage du style "chaîne n° x sur y : chaîne" (indx + 1 est utilisé pour donner le numéro actuel, en partant de 1 au lieu de 0, et StrLst.Count est utilisé pour donner le nombre de chaînes) :
for
indx := 0
to
StrLst.Count - 1
do
ShowMessage('Chaîne n° '
+ IntToStr(indx + 1
) + ' sur '
+
IntToStr(StrLst.Count) + ' : '
+ StrLst[indx]);
Voici enfin le code complet de la procédure que nous avons créée. Vous constaterez qu'elle est très courte, mais qu'elle fait intervenir nombre de notions importantes non seulement concernant les objets, mais aussi concernant les boucles.
procedure
TForm1.Button1Click(Sender: TObject);
var
StrLst: TStringList;
STemp: String
;
indx: integer
;
begin
StrLst := TStringList.Create;
STemp := ''
;
while
InputQuery('Entrez une chaîne'
, 'Chaine à ajouter à la liste :'
, STemp) do
begin
StrLst.Add(STemp);
STemp := ''
;
end
;
ShowMessage('Vous avez entré '
+ IntToStr(StrLst.Count) + ' chaînes'
);
StrLst.Sort;
for
indx := 0
to
StrLst.Count - 1
do
ShowMessage('Chaîne n° '
+ IntToStr(indx + 1
) + ' sur '
+
IntToStr(StrLst.Count) + ' : '
+ StrLst[indx]);
StrLst.destroy;
end
;
XII-C. Notions avancées sur les classes▲
Ce paragraphe présente deux aspects importants de la programmation orientée objet. Pour les débutants, ces notions sont inutiles, mais tout développeur qui veut s'initier sérieusement non seulement à l'utilisation mais aussi à la création d'objets se doit de les connaître.
XII-C-1. Hiérarchie des classes▲
Nous avons déjà parlé des classes en expliquant qu'elle sont les types à partir desquels sont déclarés les objets. Ces classes ne sont pas simplement un ensemble désorganisé dans lequel on pioche : il existe une hiérarchie. Cette hiérarchie est basée, comme les objets, sur un besoin simple : celui de ne pas réinventer constamment la roue.
XII-C-1-a. Concept général▲
Les objets sont des structures pour la plupart très complexes dans le sens où ils
possèdent un nombre important de méthodes, de variables et de propriétés.
Mettez-vous un instant à la place d'un programmeur chevronné et imaginez par
exemple tout ce qu'il faut pour faire fonctionner un simple bouton : il faut entre
autres le dessiner, réagir au clavier, à la souris, s'adapter en fonction des
propriétés. C'est une tâche, croyez-moi sur parole, qui nécessite un volume
impressionnant de code Pascal Objet. Prenons un autre composant, par exemple une
zone d'édition : elle réagit également au clavier et à la souris. Ne serait-il pas
intéressant de pouvoir « regrouper » la gestion du clavier et de la souris à un
seul endroit pour éviter de la refaire pour chaque composant (pensez qu'il existe
des milliers de composants).
De tels besoins de regroupement, il en existe énormément. Pour cela, il existe la
notion de hiérarchie parmi les classes. Cette hiérarchisation permet de regrouper
dans une classe « parent » un certain nombre de propriétés, de méthodes et de
variables. Les classes qui auront besoin d'avoir accès à ces fonctionnalités
devront simplement « descendre » de cette classe, c'est-à-dire être une classe «
descendante » de cette classe « parent ».
Le principe de la hiérarchie des classes est en effet basé sur la relation
parent-enfant ou plus exactement parent-descendant. Chaque classe possède,
contrairement à ce que l'on peut voir d'insolite dans les relations
parent-descendant humaines, une seule et unique classe parente directe. Une classe
peut avoir un nombre illimité de descendants. Lorsqu'une classe descend d'une
autre classe, la première possède absolument tout ce qui est défini par la seconde
: c'est l'héritage. La classe « descendante » est plus puissante que la
classe « parente » dans le sens où de nouvelles méthodes, variables et propriétés
sont généralement ajoutées ou modifiées.
Une telle hiérarchie impose l'existence d'un unique ancètre ultime qui n'a pas de
parent : c'est la classe « TObject » (qui possède un nom quelque peu
embrouillant). De cette classe descendent TOUTES les classes existantes sous
Delphi, et ceci plus ou moins directement (toutes les classes ne sont pas des
descendantes directes de TObject mais sont souvent des descendantes de
descendantes de... de « TObject ». Cette dernière définit les mécanismes de base
du fonctionnement d'un objet. Tous les objets sous Delphi sont d'une classe
descendante de TObject, et possèdent donc tout ce que définit TObject, à savoir le
minimum.
La classe « TObject » ne vous est pas inconnue car elle est mentionnée dans toutes
les procédures associées à des événements. Voici un extrait de code qui devrait
vous aider à y voir plus clair :
procedure
TfmPrinc.Button1Click(Sender: TObject);
begin
end
;
Dans l'extrait de code ci-dessus, la seule partie qui restait énigmatique, à
savoir « Sender: TObject », est simplement la déclaration d'un paramètre de type
TObject, c'est-à-dire que Sender est un objet de classe TObject.
Prenons une classe que vous connaissez : « TStringList ». Cette classe descend de
« TObject », mais pas directement. Elle a pour parent la classe « TStrings ». La
classe « TStrings » peut avoir un nombre illimité de classes descendantes, et a
entre autre « TStringList » comme descendante directe. « TStrings » est moins
puissante que « TStringList » car cette dernière, en plus de l'héritage complet de
ce que contient « TStrings », contient bien d'autres choses. La classe « TStrings
» a pour parent la classe « TPersistent ». Cette classe, qui est encore moins
perfectionnée que « TStrings », a enfin pour parent la classe « TObject », ce qui
termine l' « arbre généalogique » de TStringList. Voici un schémas qui résume
cette descendance :
Nous avons déjà plusieurs fois dit au cours de ce chapitre que les composants sont des objets. Il est maintenant temps de parler de la classe TComponent. Cette classe est elle aussi descendante de TObject. Voici son « arbre généalogique, pour curiosité :
L'intérêt de cette classe, c'est qu'elle est à l'origine de la définition des
composants. Un composant est en fait par définition un objet dont la classe
descend de TComponent. TComponent définit les caractéristiques de base nécessaire
à tout composant. Tous les composants que vous connaissez, et tous les autres,
sont d'une classe descendante de TComponent.
Prenons la classe TButton qui permet de créer les composants boutons bien connus.
Cette classe est bien un descendant de TComponent, mais la descendance est
indirecte puisqu'il y a des classes intermédiaires qui ajoutent chacune un grand
nombre de fonctionnalités. Voici l' « arbre généalogique » de TButton :
Comme vous pouvez le constater, TButton possède une longue ascendance pour atteindre TObject. C'est en quelque sorte le reflet de la complexité du composant : on ne se rend pas compte lorsqu'on place un bouton sur une fiche que c'est une petite usine à gaz qu'on met en fonctionnement. Puisque nous en sommes à parler des fiches, la classe TForm qui permet de définir des fiches est également une descendante de TComponent. Son arbre généalogique a été inclus ci-dessous, et les composants TStringList, TButton ont été inclus dans le schéma pour vous montrer qu'on obtient visuellement une sorte de hiérarchie entre les classes lorsqu'on en prend un nombre suffisant :
XII-C-1-b. Classes descendantes de TForm▲
Si j'insiste autant sur cette notion de hiérarchie entre les classes, c'est non seulement car sa compréhension est indispensable pour tout bon programmeur, mais surtout pour en venir à un certain bloc de code que vous connaissez pour l'avoir déjà vu un grand nombre de fois (il apparaît lorsque vous créez un projet vierge ou une nouvelle fiche, avec de petites variantes) :
type
TForm1 = class
(TForm)
private
{ Private declarations }
public
{ Public declarations }
end
;
La ligne qui nous intéresse plus particulièrement ici est : « TForm1 = class(TForm) ». Cette ligne, nous l'avons déjà expliqué, définit un nouveau type "TForm1". Ce qui est nouveau et que vous pouvez désormais comprendre, c'est que cette ligne déclare "TForm1" comme une classe (mot réservé class) descendante de la classe "TForm". Une relation de type « parent - descendant » existe entre TForm1 et TForm. Voici la relation entre les deux, exprimée comme un morceau d' « arbre généalogique » (l'ascendance de TForm a été omise pour gagner un peu de place) :
La classe "TForm1" descendante de "TForm" peut alors définir de nouveaux éléments
: méthodes, variables, propriétés, ... Les éléments ajoutés sont listés d'une
manière spécifique que nous ne décrirons pas en détail ici, entre les mots
réservés class et end.
Faites l'expérience d'ajouter un bouton et de générer la procédure associée à son
événement OnClick. L'extrait de code devient :
type
TForm1 = class
(TForm)
Button1: TButton;
procedure
Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end
;
Vous voyez que deux lignes ont fait leur apparition, l'une décrivant un élément
Button1 de type TButton et l'autre une procédure. Ces deux éléments sont deux
nouveaux éléments de la classe TForm1 : ils ne sont pas présents dans la classe
TForm. La classe TForm1 a été améliorée par rapport à TForm en ce sens qu'elle
possède deux nouveaux éléments. Le premier est une variable interne (on nommera
plutôt champ une variable interne à une classe) et le second une méthode de
la classe TForm1. Tout objet de cette classe possède ces deux éléments, en
particulier l'objet Form1 déclaré de classe TForm1.
Vous voyez peut-être ici toute la puissance de la hiérarchie des classes de Delphi
: pour créer une fiche comportant un bouton, il a suffit d'exploiter la classe
TForm qui permet d'utiliser une fiche vierge de base, de créer une classe
descendante de TForm, et d'y ajouter ce qu'on voulait en plus sur la fiche, ici un
composant Button1 de classe TButton. Sans cette hiérarchie des classes, il aurait
fallu beaucoup plus de code car il aurait fallu passer par des appels à Windows et
par des procédures relativement complexes pour un débutant.
La partie située entre class et end est la déclaration de la classe
TForm1. Dans ce morceau de code, on place les déclarations des éléments de la
classe. Les variables se déclarent comme dans un bloc var, les méthodes se
déclarent comme dans une unité, à savoir qu'on inclut la ligne de déclaration que
l'on placerait habituellement dans l'interface de l'unité. Ce bloc de code qui
contient la déclaration de la classe "TForm1" ne déclare bien entendu que ce qui
est ajouté à TForm1 en plus de ce que contient TForm. Ce bloc est divisé en
plusieurs parties, que nous allons étudier avant de pouvoir nous-mêmes ajouter des
éléments aux classes.
XII-C-2. Ajout d'éléments dans une classe▲
Un des buts majeurs de ce chapitre est de vous apprendre à ajouter vos propres
méthodes et variables aux classes. Pour l'instant, il est hors de question de créer
nos propres classes : nous allons nous contenter d'ajouter des méthodes et des
variables aux classes définissant les fiches (comme TForm1 dans le paragraphe
précédent), ce qui est déjà un bon début.
Avant de nous lancer, il faut encore connaître une notion sur les classes : celle
de sections. Le paragraphe suivant est consacré à une explication de cela. Le
paragraphe suivant explique concrètement comment ajouter des éléments à une classe.
XII-C-2-a. Sections privées et publiques d'une classe▲
Jusqu'ici, nous avons appris qu'une classe contenait des méthodes, des champs
(nous allons passez progressivement de la dénomination de variable à celle de
champ, qui est préférable dans le cadre des classes), des propriétés, et encore
d'autres choses. Tout comme les classes ne sont pas un ensemble désorganisé, il
existe à l'intérieur d'une classe un certain classement, dont le principe n'a rien
à voir avec la notion de hiérarchie des classes, et qui est également plus simple
à comprendre.
Une classe comporte en pratique un maximum de cinq sections. Chaque élément défini
par une classe est classé dans l'une de ces sections. Parmi ces cinq sections,
nous allons en étudier seulement trois ici. Les sections sont visibles au niveau
du code source car délimitées par des mots réservés à cet effet. Le début d'une
section est généralement donné par un mot réservé, sa fin est donnée par le début
d'une autre section ou la fin de la définition de la classe (par le mot réservé
end). La première section que l'on rencontre dans une classe est située
avant toutes les autres : elle est délimitée au début par class(...) et est
terminée comme indiqué ci-dessus par le début d'une autre section ou la fin de la
déclaration de la classe. Les deux autres sections sont débutées par les mots
réservés private ou public. Voici l'extrait de code utilisé dans le
paragraphe précédent, commenté pour vous montrer le début et la fin des trois
sections :
type
TfmPrinc = class
(TForm)
{ section débutée après class(...) }
{ section terminée par le début de la section "private" }
private
{ section private débutée par le mot réservé "private" }
{ Private declarations }
{ section private terminée par le début de la section "public" }
public
{ section public débutée par le mot réservé "public" }
{ Public declarations }
{ section public terminée par la fin de la classe (end) }
end
;
Le but de ces sections est de spécifier la visibilité des éléments qu'elles
contiennent vis-à-vis de l'extérieur de la classe. La première section est
réservée exclusivement à Delphi. Vous n'avez pas le droit d'y écrire quoi que
ce soit vous-même. La section suivante débutée par le mot réservé
private est nommée « section privée » de la classe : tout ce qui y est
écrit est innaccessible depuis l'extérieur, il n'y a que depuis d'autres
éléments de la classe que vous pouvez y accèder. Cette section est idéale
pour mettre des champs dont la valeur ne doit pas être modifiée depuis
l'extérieur (je vais expliquer tout cela plus en détail dans le paragraphe
suivant). La troisième et dernière section est nommée « section publique » :
tout ce qui y est écrit est accessible depuis l'extérieur. Lorsque par
exemple vous pouvez utiliser une méthode d'un objet ou d'un composant, c'est
que cette méthode est dans la section publique de la classe de ce composant.
En ce qui concerne la section réservée à Delphi, tous les
éléments qui y sont déclarés sont visibles comme s'ils étaient déclarés dans la
section publique.
Pour bien comprendre l'intérêt de telles sections, considèrons un objet simulant
une salle d'opération. La section publique comprendrait par exemple une méthode
pour lancer telle opération. Par contre, la méthode destinée à manier le scalpel
serait assurément privée, car une mauvaise utilisation aurait des conséquences
fâcheuses. De même, un champ "Patient" serait public, pour que vous puissiez
décider qui va subir l'opération, mais un champ "température" fixant la
température de la salle d'opération serait privé, car c'est un paramètre sensible
qui doit être manipulé avec précaution.
XII-C-2-b. Ajout de méthodes et de variables▲
Nous en savons assez pour commencer à ajouter des éléments aux classes. Pour le
moment, nous allons ajouter ces éléments dans la classe TForm1 descendante de
TForm. L'intérêt d'ajouter des éléments à ces classes ne vous apparaît peut-être
pas encore, mais cela viendra avec l'expérience.
Tout d'abord, le nom de cette classe (TForm1) est décidé dans l'inspecteur
d'objets : changez le nom de la fiche (propriété "name") en "fmPrinc" et observez
le changement : la classe s'appelle désormais "TfmPrinc". Ce nom est fabriqué en
préfixant un "T" au nom que vous choisissez pour la fiche. L'objet qui est alors
déclaré de classe TfmPrinc est alors bien "fmPrinc", nom que vous avez choisi pour
la fiche et qui est en fait, comme vous le saviez déjà, le nom de la variable qui
stocke la fiche :
type
TfmPrinc = class
(TForm)
private
{ Private declarations }
public
{ Public declarations }
end
;
var
fmPrinc: TfmPrinc;
Nous allons maintenant ajouter un champ nommé fTest de type Integer (le préfixe f ou F est souvent employé pour préfixer les identificateurs qui servent à nommer les champs des classes) à la classe TfmPrinc. Nous allons en outre faire cet ajout dans la section publique de la classe. Pour cela, complètez sous Delphi votre code en prenant comme modèle l'extrait suivant où la modification a été effectuée :
type
TfmPrinc = class
(TForm)
private
{ Private declarations }
public
{ Public declarations }
fTest: Integer
;
end
;
var
fmPrinc: TfmPrinc;
La classe TfmPrinc déclarant un champ fTest de type Integer, tous les objets de cette classe et des éventuelles classes descendantes de TfmPrinc contiendront une variable fTest. C'est le même principe avec les éléments rajoutés par Delphi tels les composants ou les méthodes associées aux événements. Ces éléments sont en fait déclarés dans la classe et sont donc accessibles dans l'objet qui est déclaré de cette classe. Posez donc un bouton nommé "btAction" sur la fiche et générez la procédure (i.e. la méthode) associée à son événement OnClick. La déclaration de la classe TfmPrinc est alors :
TfmPrinc = class
(TForm)
btAction: TButton;
procedure
btActionClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
fTest: Integer
;
end
;
Vous remarquez que la méthode btActionClick et le champ btAction sont déclarés avant les sections privées et publiques, dans l'espace réservé à Delphi. Ces deux éléments, ainsi que le champ fTest, font partie de la classe TfmPrinc. De ce fait, la méthode btActionClick a accès non seulement à btAction puisque ce champ est public (il est déclaré dans la section réservée à Delphi, qui est analogue à la section publique en terme de visibilité), mais également au champ fTest puisque la méthode est à l'intérieur de la classe. Nous allons modifier la valeur du champ fTest et afficher cette valeur depuis la procédure pour démontrer cela. Utilisez l'extrait de code ci-dessous pour complèter votre procédure btActionClick :
procedure
TfmPrinc.btActionClick(Sender: TObject);
begin
fTest := 10
;
ShowMessage(IntToStr(fTest));
end
;
Vous pouvez constater qu'on utilise fTest comme une propriété, mais ce n'en est
pas une, et fTest ne peut pas non plus être en lecture seule.
Voilà, vous avez modifié une classe pour la première fois. Maintenant, il faut
savoir que le champ que nous avons utilisé était très simple, mais que rien
n'empêche d'utiliser des types comme les tableaux, les enregistrements, les
pointeurs et les objets.
Nous allons maintenant ajouter une méthode à la classe TfmPrinc. Pour goûter à
tout, nous allons la déclarer dans la section publique de la classe et déplacer
fTest dans la section privée. La méthode, nommée SetTest, servira en quelque sorte
d'intermédiaire entre l'extérieur de la classe (qui aura accès à la méthode mais
pas au champ) et l'intérieur pour nous permettre de refuser certaines valeurs.
Voici la nouvelle déclaration de la classe :
TfmPrinc = class
(TForm)
btAction: TButton;
procedure
btActionClick(Sender: TObject);
private
{ Private declarations }
fTest: Integer
;
public
{ Public declarations }
procedure
SetTest(Valeur: Integer
);
end
;
Maintenant que la méthode est déclarée, il va falloir écrire son code. Pour cela, il ne faut pas oublier que SetTest est une méthode interne à TfmPrinc et qu'il faudra, pour écrire son code source, lui donner son nom qualifié. Pour ceux qui ne se rappelent pas de ce que ça signifie, il faudra précéder le nom de la méthode par le nom de la classe et un point. Il va de soi que l'ensemble sera écrit dans la partie implémentation de l'unité. Voici donc le squelette du code de la méthode :
procedure
TfmPrinc.SetTest(Valeur: Integer
);
begin
end
;
Pour les heureux posseseurs de Delphi 5, il n'est pas indispensable d'insèrer
vous-même ce code : placez-vous sur la ligne de la déclaration de la méthode et
utilisez le raccourci clavier Ctrl + Maj + C. Ceci aura pour effet d'écrire
automatiquement le code présenté ci-dessus et de placer le curseur entre le
begin et le end.
Nous allons complèter cette méthode. Etant donné qu'elle est interne à la classe
TfmPrinc, elle a accès au champ fTest. Nous allons fixer fTest à la valeur
transmise, uniquement si cette valeur est supérieure à la valeur actuelle. Voici
le code à utiliser :
procedure
TfmPrinc.SetTest(Valeur: Integer
);
begin
if
Valeur >= fTest then
fTest := Valeur;
end
;
Afin de tester la méthode, nous allons l'appeler plusieurs fois avec diverses valeurs et afficher ensuite la valeur résultante de fTest. Voici un nouveau contenu pour la procédure btActionClick :
procedure
TfmPrinc.btActionClick(Sender: TObject);
begin
SetTest(10
);
SetTest(30
);
SetTest(20
);
ShowMessage(IntToStr(fTest));
end
;
Le message résultant affiche bien évidemment 30. Ce n'est encore une fois pas le
résultat qui compte. mais la méthode employée.
Comme vous pouvez le constater, modifier une classe existante n'est pas très
compliqué. Lorsque l'on ajoute un élément à une classe, il faut cependant essayer
de respecter une règle : on place en priorité les éléments dans la section privée,
et on ne les place dans la section publique que lorsque c'est nécessaire.
XII-C-3. Paramètres de type objet▲
XII-C-3-a. Explications générales▲
Maintement que vous avez de bonnes bases sur les objets et les classes, il est un
sujet qu'il nous est enfin possible d'aborder. Ce sujet requièrerait d'ailleurs de
plus grandes connaissances sur les objets, mais nous allons tenter de faire
sans.
Comme le titre de ce paragraphe le suggère, il s'agit ici des paramètres - de
procédures et de fonctions - dont les types sont des classes (ce qui implique donc
que les paramètres eux-mêmes sont des objets). Voici immédiatement un premier
exemple qui devrait vous être quelque peu familier :
procedure
TForm1.Button1Click(Sender: TObject);
begin
end
;
Cet extrait de code, comme vous vous en doutez (j'espère !), est le squelette
d'une procédure associée à un événement OnClick. Cette procédure, qui fait, soit
dit en passant partie de la définition de la classe "TForm1", a un trait
particulier qui nous intéresse davantage ici : elle accepte un unique paramètre de
type "TObject". Or "TObject" est un objet, ce qui fait que le paramètre appelé
"Sender" est de type objet.
Cet exemple est toutefois peu intéressant car la classe "TObject" n'est pas très
développée. Imaginons maintenant que nous ayons besoin de changer le texte d'un
bouton sur une fiche, mais sans utiliser le nom de ce bouton. Ceci permettrait par
exemple de changer le texte de n'importe quel bouton avec cette procédure.
Nommons-là "ChangeTexte". Cette procédure doit agir sur un bouton, c'est-à-dire
sur un objet de classe "TButton". Le premier paramètre de la procédure sera donc
de cette classe. Nous allons également transmettre à la procédure le texte à
écrire sur le bouton, ce qui se fera par un paramètre de type chaîne. Voici le
squelette de cette procédure :
procedure
ChangeTexte(Bt: TButton; NouvTxt: String
);
begin
end
;
Depuis cette procédure, "Bt" doit être considéré comme un véritable objet de classe "TButton", comme le paramètre "NouvTxt" qui est considéré comme une vraie chaîne de caractères. On a tout à fait le droit d'accèder à la propriété "Caption" de "Bt" puisque c'est un bouton. Nous allons donc écrire l'unique instruction de la procédure ainsi :
procedure
ChangeTexte(Bt: TButton; NouvTxt: String
);
begin
Bt.Caption := NouvTxt;
end
;
Il faudra cependant faire très attention avec les paramètres de type objet, car
contrairement aux types simples qui ont toujours une valeur, et similairement aux
pointeurs qui peuvent valoir nil, les paramètres de type objet peuvent
également ne pas être corrects, s'ils sont non construits par exemple. Un bon
moyen pour savoir si un objet est valide est d'utiliser la fonction "Assigned".
Cette fonction particulière qui accepte un peu n'importe quoi comme paramètre
(pointeurs et objets sont acceptés) renvoie un booléen renseignant sur l'état du
paramètre : faux indique pour un pointeur qu'il vaut nil, et pour un objet
qu'il est invalide, vrai indique que le pointeur n'est pas nil, ou qu'un
objet est correctement construit.
Nous éviterons donc des erreurs en modifiant la procédure "ChangeTexte" de cette
manière :
procedure
ChangeTexte(Bt: TButton; NouvTxt: String
);
begin
if
Assigned(Bt) then
Bt.Caption := NouvTxt;
end
;
Avec cet exemple, vous voyez que l'on manipule les paramètres de type objet comme les autres paramètres, hormis le fait qu'on prend un peu plus de précaution en les manipulant, ce qui est tout à fait compréhensible vu la complexité des classes. Nous allons maintenant voir que cette apparente simplicité cache en fait bien plus.
XII-C-3-b. Envoi de paramètres de classes différentes▲
Dans l'exemple de la procédure "Button1Click" ci-dessus, on serait en droit de
croire que "Sender" est alors de classe "TObject", ce qui serait tout à fait
légitime vu ce qui précède dans tout ce chapitre. En fait, ce n'est pas toujours
le cas. En réalité, "Sender" peut effectivement être de classe "TObject", mais
également de toute classe descendante de la classe
"TObject". Ainsi, "Sender" peut très bien être de classe "TComponent" ou
"TButton" par exemple, mais aussi "TObject". Ceci signifie qu'on peut transmettre
en tant que paramètre de type classe n'importe quel objet dont la classe est ou
descend de la classe du paramètre dans la déclaration de la
procédure/fonction.
Pas d'affolement, je m'explique : considérons une procédure "Toto" qui a un
paramètre "Obj" de classe "TMachin". On peut alors appeler "Toto" en donnant comme
paramètre soit un objet de classe "TMachin", soit un objet dont la classe descend
de "TMachin". Par exemple, si la classe "TSuperMachin" descend de "TMachin", alors
tout objet de classe "TSuperMachin" peut être transmis en tant que paramètre de
"Toto". Voici une déclaration possible pour "Toto" :
procedure
Toto(Obj: TMachin);
Mais cette liberté dans le choix des classes des paramètres objets a une limite :
à l'intérieur de la procédure, un paramètre de classe "TMachin" n'est plus de
classe "TSuperMachin". Ceci signifie que même si vous transmettez un paramètre de
classe "TSuperMachin" à la procédure "Toto", cette dernière considérera que votre
paramètre est de type "TMachin", et perdra donc tout ce qui a été ajouté à
"TMachin" pour en faire "TSuperMachin".
Imaginons par exemple qu'une méthode "SuperMethode" soit ajoutée à "TSuperMachin"
(elle ne figure pas dans "TMachin", nous supposerons également pour faire simple
que cette méthode n'a pas de paramètres). Supposons aussi que nous avons un objet
"SuperObjTest" de classe "TSuperMachin". Depuis l'extérieur de la procédure
"Toto", vous pouvez appeler "SuperMethode" (à condition toutefois que cette
méthode soit dans la section publique de la classe "TSuperMachin") de la manière
habituelle, à savoir :
SuperObjTest.SuperMethode;
Depuis l'intérieur de la procédure "Toto", "Obj" est considéré de classe "TMachin", c'est-à-dire que si vous écrivez ce qui est présenté ci-dessous, vous aurez une erreur :
procedure
Toto(Obj: TMachin);
begin
Obj.SuperMethode; { <-- Ceci est interdit !!! }
end
;
Pourquoi une erreur ? Simplement parce que la méthode "SuperMethode" n'est pas
définie au niveau de la classe "TMachin", mais seulement par l'une de ses classes
descendantes. Un objet de classe "TMachin" (Obj) ne possède pas de méthode
"SuperMethode", et donc une erreur est affichée en vous indiquant que la méthode
"SuperMachin" est inconnue.
Ceci termine la partie cours de ce premier chapitre consacré aux objets. Le
prochain chapitre va vous permettre de respirer un peu car le sujet évoqué, les
fichiers, bien qu'important, est à mon sens un sujet plus abordable que les objets.