Developpez.com

Plus de 2 000 forums
et jusqu'à 5 000 nouveaux messages par jour

Guide Pascal et Delphi


précédentsommairesuivant

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 :

 
Sélectionnez

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 :

 
Sélectionnez

...
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.

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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.

 
Sélectionnez

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 :

 
Sélectionnez

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) :

 
Sélectionnez

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 :

 
Sélectionnez

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.

 
Sélectionnez

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 :

  1. initialisation de l'objet qui gérera la liste
  2. une boucle demandant un nombre à priori inconnu de chaînes
  3. l'affichage du nombre de chaînes
  4. le tri alphabétique de la liste
  5. l'affichage d'un nombre connu de chaînes
  6. 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 :

 
Sélectionnez

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 :

 
Sélectionnez

Cont := True;
while Cont do
  begin
    Cont := InputQuery({...});
    ...
  end;

ou même comme cela :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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) :

 
Sélectionnez

for indx := 0 to StrLst.Count - 1 do
  ShowMessage('Chaîne  ' + 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.

 
Sélectionnez

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  ' + 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 :

 
Sélectionnez

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 :

Image non disponible

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é :

Image non disponible

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 :

Image non disponible

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 :

Image non disponible
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) :

 
Sélectionnez

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) :

Image non disponible

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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" :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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.


précédentsommairesuivant

  

Copyright © 2008 Frédéric Beaulieu. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.