5.2 Modularité et POO en Delphi



Plan du chapitre:

1. Description générale de Delphi
 

1.1 L'application Delphi
1.2 Les fiches et les contrôles


2. Les modules dans Delphi
 

2.1 Partie " public " d’une UNIT : " Interface "
2.2 Partie " privée " d’une UNIT : " Implementation "
2.3 Initialisation et finalisation d’une UNIT


3. Delphi et la POO
 

3.1 Les éléments de base
3.2 Fonctionnalités
3.3 Les classes 3.3.1 Méta-classe
3.3.2 Classe amie
3.4 Le modèle objet
3.5 Les objets
3.6 Encapsulation
3.7 Héritage
3.8 Surcharge de méthode


4. Les propriétés en Delphi
 

4.1 Définition
4.2 Accès par read/write aux données d'une propriété
4.3 Propriétés tableaux
4.4 Surcharge de propriété



Introduction

Delphiä de Borland est un RAD visuel fondé sur une extension orientée objet, visuelle et événementielle de Pascal. Pascal est le langage utilisé pour l’initiation dans 80% des établissements d’enseignement européens. Le RAD Delphi est un prolongement intéressant de ce langage. Nous allons explorer certains aspects les plus importants et significatifs du pascal objet de Delphi. Le langage pascal de base étant supposé connu par le lecteur, nous souhaitons utiliser ce RAD visuel en réutilisant du savoir faire pascal tout en y rajoutant les nouvelles possibilités offertes par Delphi.
 

1. Description minimale de Delphi ...
 
La version 7 utilisée pour écrire les exemples est la dernière disponible sur Windows, mais tous les exemples sont écrits avec les fonctionnalités générales de Delphi ce qui permet de les compiler sur n'importe quelle version  de Delphi depuis la version 5.


1.1 L'application Delphi

Une application console (non fenêtrée) Delphi se compose d'un projet "xxx.dpr" et d'au minimum un fichier d'Unit "xxx.pas" pour le code source. Lors de la compilation d'un projet Delphi engendre un code "xxx.exe" directement exécutable.

 


Tous les projets Delphi s'exécutent sous windows sans aucune Dll supplémentaire autre que celles que vous programmerez vous-même :

Une application fenêtrée Delphi se compose d'un projet "xxx.dpr" et d'au minimum deux fichiers de fiche "xxx.dfm" pour la description et "xxx.pas" et d'Unit pour le code source.

 
Ci-contre un projet minimal Delphi comportant une fiche principale :

Le projet se dénomme Project1.dpr

La fiche principale Form1 et le code source de l'application sont rangés dans la Unit Unit1.pas.

La description de la fiche Form1 et de ce qu'elle contient se trouve dans un fichier nommé Unit1.dfm.



A quoi sert une fiche ?



Les systèmes d'exploitation actuels sont dit fenêtrés au sens où ils fournissent un mode de communication avec l'homme fondé sur la notion de fenêtre, Windows en est un exemple.

La première action à entreprendre lors du développement d'une application Interface Homme Machine (IHM) avec un langage de programmation, est la création de l'interface de l'application et ensuite les interactions avec l'utilisateur. Le langage de programmation doit donc permettre de construire au moins une fenêtre.

 

En Delphi pour créer une IHM, il faut utiliser des fiches (ou fenêtres) et des contrôles.

Ces fiches sont des objets au sens informatique de la POO, mais elles possèdent une représentation visuelle.

La fiche Form1 du projet Projet1 minimal :

     

    1.2 Les fiches et les contrôles

    Chaque fiche est en fait un objet instancié à partir de la classe interne des TForm de Delphi. Cette classe possède des propriétés(attributs) qui décrivent l'apparence de la feuille, des méthodes qui décrivent le comportement de la feuille et enfin des gestionnaires d'événements (pointeurs de méthodes) qui permettent de programmer la réaction de la fiche aux événements auxquels elles est sensible.

    Sur une fiche vous déposez des contrôles qui sont eux aussi d'autres classes d'objets visuels, mais qui sont contenus dans la fiche.

    Ci-dessous la palette des composants de Delphi déposables sur une fiche :


     

    Que se passe-t-il lors du dépôt des 3 contrôles ?


code dans la fiche Form1 avant dépôt
code dans la fiche Form1 après dépôt
unit Unit1;
interface
uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TForm1 = class(TForm)
  private
    { Déclarations privées }
  public
    { Déclarations publiques }
  end;
var
  Form1: TForm1; 
implementation
{$R *.DFM}
end.
unit Unit1;
interface
uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Edit1: TEdit;

  private
    { Déclarations privées }
  public
    { Déclarations publiques }
  end;
var

  Form1: TForm1; 
implementation
{$R *.DFM}
end.


    Il existe dans Delphi une notion de classe conteneur, la fiche (classe TForm) en est le principal représentant. Delphi est un générateur automatique de programme source Pascal objet et dès l'ouverture du projet, il définit une classe conteneur TForm1 qui hérite de la classe TForm et qui au départ ne contient rien :

     TForm1 = class(TForm)
      private
        { Déclarations privées }
      public
        { Déclarations publiques }
      end;

    Lorsque nous déposons les 3 contrôles Button1, Edit1, Memo1, ce sont des champs objets qui sont automatiquement ajoutés par Delphi dans le code source de la classe :

      TForm1 = class(TForm)
        Memo1: T Memo;
        Button1: TButton;
        Edit1: TEdit;
      private
        { Déclarations privées }
      public
        { Déclarations publiques }
      end;

    Donc l'environnement Delphi, n'est pas seulement un langage de programmation, mais aussi un générateur de programme à partir de dépôt de composants visuels, c'est la fonction d'un système RAD (Rapid Application Developpement).

    La fiche Form1 avec son explorateur de code et diagramme :

     

    Pour une utilisation complète de Delphi, nous renvoyons le lecteur à la documentation du constructeur et aux nombreux ouvrages existants, nous attachons par la suite à faire ressortir et à utiliser les éléments de Delphi qui concernent la programmation modulaire et la programmation objet.
     
     
     

    2. Les modules dans Delphi ...
     

    Le module en Delphi est représenté par une unité compilable séparément de tout programme et stockable en bibliothèque. Une Unit comporte une partie " public " et une partie " privé ". Elle implante donc l’idée de module et étend la notion de bloc (procédure ou fonction) en Pascal. Elle peut contenir des descriptions de code simple ou de classe.

    Chaque unité est stockée dans un fichier distinct et compilée séparément ; les unités compilées (les fichiers xxx.DCU) sont liées pour créer une application.

    Les unités en Delphi permettent :
    • De diviser de grands programmes en modules qui peuvent être modifiés séparément.
    • De créer des bibliothèques qui peuvent être partagées par plusieurs programmes.
    • De distribuer des bibliothèques à d'autres développeurs sans leur donner le code source.

    Pour générer un code éxécutable à partir d'un projet comportant plusieurs unités, le compilateur Delphi doit disposer, pour chaque unité, soit du fichier source xxx.PAS, soit du fichier XXX.DCU résultant d'une compilation antérieure. Cette dernière possibilité permet de fournir des unités compilées à d'autres personnes sans leur fournir le code source.

    Syntaxe d'une unité :


 

 Unit Truc;
  <partie public>
  <partie privée>
  <initialisation>
 end.



Utilisation d'une unité :


   

 


Le programme principal se nomme le projet, il est rangé dans le fichier "xxx.dpr". 


Ici le programme principal utilise 3 Unit :

  UnitA ,
  UnitB 
  et UnitC


    Squelette du code associé au schéma précédent :

unit UnitA;
interface

implementation

end.
 

unit UnitB;
interface

implementation

end.

unit UnitC;
interface

implementation

end.

Program project1;
Uses UnitA, UnitB, UnitC;

Begin

end.

    Fichiers disque (sources et compilés) associés au code précédent :

project1.dpr
UnitA.pas
UnitA.pas
UnitA.pas


Après compilation
------------>
project1.exe
UnitA.dcu
UnitA.dcu
UnitA.dcu

    Pour ajouter au projet une nouvelle Unité : 



On peut utiliser l'environnement d'ajout de Delphi :



qui crée par exemple un fichier Unit2.pas contenant le squelette de la Unit2 et rajoute automatiquement cette unit au programme principal.
On peut écrire soi-même un fichier texte contenant la unit :

unit Unit2;
interface

implementation

end.


Et rajouter soi-même au texte source du programme principal la unit :

Program project1;
Uses Unit2 ;

Begin

end.


     

    2.1 Partie " public " d’une UNIT : " Interface "

    Correspond exactement à la partie publique du module représenté par la UNIT. Cette partie décrit les en-têtes des procédures et des fonctions publiques et utilisables par les clients. Les clients peuvent être soit d’autres procédures Delphi, des programmes Delphi ou d’autres Unit.

    La clause Uses XXX dans un programme Delphi, permet d’indiquer la référence à la Unit XXX et autorise l’accès aux procédures et fonctions publiques de l’interface dans tout le programme.
     

    Syntaxe de l'interface :


     
     
     

    2.2 Partie " privée " d’une UNIT : " Implementation "

    Correspond à la partie privée du module représenté par la UNIT. Cette partie intimement liée à l’interface, contient le code interne du module. Elle contient deux sortes d’éléments : les déclarations complètes des procédures et des fonctions privées ainsi que les structures de données privées. Elle contient aussi les déclarations complètes des fonctions et procédures publiques dont les en-têtes sont présentes dans l’interface.

    Syntaxe de l'implementation :


     
     
     

    2.3 Initialisation et finalisation d’une UNIT
     

    Syntaxe de l'initialisation :

    La partie initialisation d'une unité en Delphi comporte deux sous parties Initialization et Finalization la seconde étant optionnelle:

    Initialization
    Il est possible d'initialiser des variables et d'exécuter des instructions au lancement de l'UNIT. Elles correspondent à des instructions classiques Pascal sur des données publiques ou privées de la Unit (initialisation de tableaux, mise à zéro de divers indicateurs, chargement de fichiers etc...).

    Finalization
    Une fois que le code d'initialisation d'une unité a commencé à s'exécuter, la section de finalisation correspondante si elle existe, s'exécute obligatoirement à l'arrêt de l'application (libération de mémoire, de fichiers, récupération d'incidents etc...).


    Exemple de programme console modulaire

Unit Delphi Uratio du TAD rationnel  
Programme principal Delphi utilisant Uratio
unit  Uratio ;
{unité de rationnels spécification classique ZxZ/R}
interface
 type
  
rationnel  =
  
record
   
num integer ;
   
denom integer
  
end;
  procedure 
reduire ( var  r rationnel) ;
  procedure 
addratio (a, b rationnel ; var  s rationnel) ;
  procedure 
divratio (a, b rationnel ; var  s rationnel) ;
  procedure 
mulratio (a, b rationnel ; var  s rationnel) ;
  procedure 
affectQ( var  s rationnel b rationnel) ;
  procedure 
opposeQ(x : rationnel ;var  s : rationnel) ;
   
implementation
 procedure 
reduire ….
 
procedure  addratio ….
 
Procedure  divratio ….
 
procedure  mulratio ….
 
procedure  affectQ….
 
procedure  opposeQ….
end.    
program  essaiRatio ;
 
{programme de test de la unit Uratio }
 
 {$APPTYPE CONSOLE}
 
 
uses  SysUtils , Uratio ;
 var
  
r1, r2, r3, r4, r5 rationnel ;
  
  begin
   
r1.num  := 18 ;
   
r1.denom  :=  15 ;
   
r2.num  :=  7 ;
   
r2.denom  :=  12 ;
   
addratio(r1, r2, r3) ;
   
writeln ( '18/15 + 7/12 = ' , r3.num,  '/' , r3.denom) ;
   
mulratio(r1, r2, r4) ;
   
writeln ( '18/15 * 7/12 = ' , r4.num,  '/' , r4.denom) ;
   
divratio(r1, r2, r5) ;
   
writeln ( '18/15 / 7/12 = ' , r5.num,  '/' , r5.denom) ;
   
r1.num  :=  72 ;
   
r1.denom  :=  60 ;
   
affectQ(r3,r1) ;
   
reduire(r1) ;
   
writeln ( '72/60 = ' , r1.num,  '/' , r1.denom) ;
   
writeln ( 'avant réduction ' , r3.num,  '/' , r3.denom) ;
  end.

Exemple fiche : le programme lançant une fiche vierge
unit  Unit1 ;
 interface
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs ;

 type
  
TForm1  class (TForm)
 
private
 
{ Déclarations privées }
 
public
 
{ Déclarations publiques }
end;
 var
  
Form1 TForm1
implementation
{$R *.DFM}
end.
program  Project1 ;
  
 uses  
Forms,
  Unit1 
in  'Unit1.pas'  {Form1} ;
  
  
{$R *.res}
  
  
begin
   
Application.Initialize ;
   
Application.CreateForm(TForm1, Form1) ;
   
Application.Run ;
  end.

      résultat d'exécution :
     


     
     

    3. Delphi et la POO ...
     

    Notre objectif est d'observer comment Delphi implante les notions contenues dans la P.O.O. C'est un langage orienté objet, dans ce domaine il possède des fonctionnalités équivalentes à C++ et java. Il est d'ailleurs utilisé comme langage de développement professionel sous windows. Borland a sorti en 2001 une version Delphi sous Linux dénommée Kylix permettant le portage d'applications écrites en Delphi sous windows sous le système d'exploitation Linux (moyennant les contraintes classiques de non utilisation d'outils de bas niveau). Avec la version Delphi 8 .Net sortie en 2004, il possède des fonctionnalités équivalentes à celle du langage C# de Microsoft.
     
     

    3.1 Les éléments de base

    Sachons que dans Delphi tout est objet, des contrôles de la VCL (Visual Component Library commportant plus de 600 classes dont environ 75 sont visuelles, les autres étant non visuelles ou concernant des objets de service).

    Delphi possède de par son fondement sur Object Pascal tous les types prédéfinis du pascal et les constructeurs de types (supposés connus du lecteur), plus des types prédéfinis étendus spécifique à Delphi. Ce qui signifie qu'il est tout à fait possible de réutiliser en Delphi sans effort de conversion ni d'adaptation tout programme pascal ISO ou UCSD déjà écrit.

    Les types integer, real et string sont des types génériques c'est à dire qu'ils s'adaptent à tous les types d'entiers, de réels et de chaînes de Delphi.

    Par exemple les entiers de base de Delphi suivants :

Shortint       
-128..127
8 bits signé
Smallint               
-32768..32767  
16 bits signé
Longint       
-2147483648..2147483647
32 bits signé
Byte           
0..255
8 bits non signé
Word           
0..65535
16 bits non signé
Longword       
0..4294967295 
32 bits non signé

    peuvent être affectés à une variable  x : integer car c'est un type générique.

    Généricité

var x : integer ;
a : Longint; b : Longword; c: Byte; d: Word; e: Smallint; f: Shortint;
x := a ; x := b ; x := c ; x := d ; x := e ; x := f ;

Type polymorphe


Delphi
dispose d'un type générique "variant" polymorphe sur les types de données prédéfinis de Delphi.


Un variant peut s'adapter ou se changer en pratiquement n'importe quel type de Delphi.




Exemple de variant :

Var x : variant;

begin
 x:='abcdefgh';// x est une string
 x:=123.45;  // x est un real
 x:=true;   // x est un booléen
 x:=5876   // x est un integer
 etc...


    Paramètres

      Les passages des paramètres s'effectuent fondamentalement selon les deux modes classiques du pascal : le passage par valeur (pas de mot clé), le passage par référence (mot clé Var). Par défaut si rien n'est spécifié le passage est par valeur.

      Améliorations de ces passages de paramètres :
      Delphi autorise un mode de passage dit des paramètres constants permettant une sécurité accrue au passage par valeur (mot clé Const).

      Delphi dispose d'une autre amélioration concernant le passage par référence : le passage par sortie (mot clé out), qui indique simplement à la procédure où placer la valeur en sortie sans spécifier de valeur en entrée, qui si elle existe n'est pas utilisée.

    Exemple d'utilisation un tri récursif sur un tableau de variant :
    (les paramètres sont tous passés par référence )
    const n=100;

    type Tableau =array[0..n] of variant;

    procedure quicksort (var G,D:integer; var tabl:Tableau);
    var i , j : Integer;
        x , w : Variant;
    begin
      i := G;
      j := D;
      x := tabl[(i + j) div 2];
      repeat
        While tabl[i] < x do  i := i + 1;
      While tabl[j] > x do  j := j - 1;
        If i <= j Then
        begin
            w := tabl[i];
            tabl[i] := tabl[j];
            tabl[j] := w;
            i := i + 1;
            j := j - 1
        End;
      Until i > j;
      If G < j Then quicksort(G, j, tabl);
      If D > i Then quicksort(i, D, tabl)
    End;


    La procédure générique quicksort permet de trier un tableau de variant, elle trie en fait, des entiers longs ou courts, des réels simples ou doubles et des chaînes de caractères.

    Grâce à son pouvoir polymorphe un variant peut devenir au choix soit:

Entier long
Entier court
Un réel simple ou double
Une chaîne de caractères.


    Ce qui revient à dire que la procédure générique quicksort  permet de trier un tableau de :

Entiers longs
Entiers courts
Réels simples ou doubles
Chaînes de caractères.


    Dans le cas où le type variant n'existe pas, il faut au moins 3 procédures différentes quicksortXXX qui diffèrent par le type de leur paramètres, mais avec le même code :

procedure quicksortEntier (sur un tableau d'integer)
procedure quicksortReel (sur un tableau de real)
procedure quicksortString (sur un tableau de string)

    Le nombre de paramètres effectifs en Delphi doit être exactement le même que celui des paramètres formels.
     
     

    3.2 Fonctionnalités du pascal objet
     
    Delphi est un langage à structure de blocs complet. Sa gestion est identique à celle du langage pascal. 

Dans un langage à structure de bloc comme Delphi, la mémoire centrale contient deux entités fondamentales : le tas et de pile d'exécution.

Le tas est une structure de données déjà étudiée (arbre parfait partiellement ordonné géré dans un tableau).

La pile d'exécution est une pile LIFO. 


    La pile d'exécution de Delphi a une taille paramétrable par le programmeur (maximum=2 147 483 647 octets),elle permet toutes les récursivités utiles.

    Ci-dessous l'illustration du fonctionnement du tas et de la pile dans le cas de l'appel d'une procédure P0 qui appelle une procédure P1 qui appelle elle-même une procédure P2:


Le tas contient les structures dynamiques et les codes.

La pile d'exécution contient les contextes des procédures appelées.


procedure P0;
begin
  P1
end ;

procedure P1;
begin
  P2
end ;


    Delphi est un langage acceptant la récursisvité


    La récursivité d'une procédure ou d'une fonction est la capacité que cette fonction/procédure a de s'appeler elle-même.

    Soit par exemple la somme des n premiers entiers définie par la suite récurrente Sn :

Sn = Sn-1 + n
S0 = 0


    Ci-dessous une fonction de calcul récursif de la somme des n premiers entiers :

     
    Function recur(n : integer) : integer;
    begin
     If n = 0 Then
      recur := 0
     Else
      recur := n + recur(n - 1)
    End;

    Delphi affiche un message d'erreur de pile pleine sur l'appel recur(129937), donc une profondeur de 129936 appels recursifs a été atteinte sur ce programme avec le paramètrage standard fournit de 1 Mo comme taille maximum.


     

       
    3.3 Les classes

    La notion de classe est présente dans Delphi. Les classes sont déclarées comme des types et contiennent des champs, des méthodes et des propriétés. Il existe une classe d'objet primitive appelée TObject , qui est l'ancêtre de toutes les autres classes de Delphi.

Ci-dessous un extrait de la hiérarchie des classes VCL dans Delphi :


    Comment déclarer une classe en Delphi

    Un type classe doit être déclaré et nommé avant de pouvoir être instancié. Une classe est considérée par Delphi comme un nouveau type et doit donc être déclarée là où en Pascal l'on construit les nouveaux types : au paragraphe des déclarations à l'alinéa de déclaration des types.

    Les types classes peuvent être déclarés pratiquement partout où le mot clé type est autorisé à l'exception suivante : ils ne doivent pas être déclarés à l'intérieur d'une procédure ou d'une fonction.

    Les deux déclarations ci-dessous sont équivalentes :
     
    Type
     ma_classe = class
       {déclarations de champs
       {spécification méthodes }
       {spécification propriétés }
     end;
    Type
     ma_classe = class(Tobject)
       {déclarations de champs
       {spécification méthodes }
       {spécification propriétés }
     end;
     

    Un type classe doit être déclaré et nommé avant de pouvoir être instancié. Les types classes peuvent être déclarés pratiquement partout où le mot clé type est autorisé à l'exception suivante : ils ne doivent pas être déclarés à l'intérieur d'une procédure ou d'une fonction.
     

    3.3.1 Méta-classe
     
    Delphi autorise la construction de méta-classes. Une Méta-classe est un générateur de classe. En Delphi les méta-classes sont utilisées afin de pouvoir passer des paramètres dont la valeur est une classe dans des procédures ou des fonction. 

    Les méta-classes en Delphi sont représentées par une référence de classe :

      Type TMetaWinClasse = class of TWinControl;
      var UnMetaWin : TMetaWinClasse;

    La variable x de classe TMetaWinClasse, peut contenir une référence sur n'importe quelle classe de TWinControl :  x :=Tmemo; x :=TEdit; x :=TButton; …


    Exemple d'utilisation de la notion de méta-classe pour tester le type réel du paramètre effectif lors de l'appel de TwinEssai :

procedure TwinEssai (UnWinControl : TmetaWinClasse );
begin
 if  UnWinControl =TEdit then
  else if  UnWinControl=TMemo then
   ....etc
end;


    A comparer avec l'utilisation de l'opérateur is sur le même problème :

        procedure TwinEssai (UnWinControl : TWinControl);
        begin
         if
          UnWinControl is TEdit then
          else if  UnWinContro is TMemo then
           ....etc
        end;

    En passant à un constructeur un paramètre de méta-classe on peut alors construire des objets dont la classe ne sera connue que lors de l'exécution.
     

    3.3.2 Où déclarer une classe en Delphi ?
     
    • Les classes sont déclarées dans des unit.
    • Toutes les classes déclarées dans la même unit sont amies, c'est à dire que chacune d'elle a accès aux membres privés de toutes les autres classes declarées dans cette unit.
    • Si l'on souhaite avoir n classes non amies, il faut les déclarer chacune dans une unit séparée.

     

    3.4 Le modèle objet de Delphi

    Le modèle physique choisi est celui de la référence :


    Ci-dessous une carte mémoire fictive d'un processus (une application Delphi classique à un seul thread):

    La simplicité du modèle (semblable aux variables dynamiques ou pointeurs du pascal) permet de dire qu’en Delphi les pointeurs sont sous-jacents mais entièrement encapsulés.

    Ce modèle impose une contrainte d’écriture en découplant la déclaration d’objet et sa création.
     
     

    3.5 Les objets

    Un objet en Delphi suit très exactement les définitions générales d'objets.

    Un objet en Delphi est une instance d'une classe, il contient des variables pascal (champs) et des procédures et des fonctions (méthodes)et des propriétés. Nous verrons plus loin que ces propriétés peuvent être de différentes catégories, en première lecture nous dirons que ce sont des champs possédant des spécificateurs d'accès.

 

    Les classes et les objets sont déclarés dans les unités (dans la partie déclaration de l'interface ou de l'implementation de l'unité), le code des méthodes est défini uniquement dans la partie implementation de l'unité.

    Constructeur/Destructeurs d'objets

    En Delphi, chaque variable d'instance (objet instancié) doit obligatoirement être initialisée et peut être détruite lorsqu'elle n'est plus utile. Tout objet doit être d'abord construit avant son utilisation. Ceci se fait à l'aide d'une méthode spécifique déclarée par le mot clef constructor.

    La destruction d'une instance s'effectue par une méthode aux propriétés identiques à un constructor et elle se dénomme un destructor.

    • Par défaut vos classes disposent (provenant de la classe mère TObject) d'une méthode permettant la construction des objets de la classe : cette méthode de type constructor est dénommée Create. Ce genre de méthode assure la création de l'objet physique en mémoire et sa liaison avec l'identificateur déclaré dans le code (l'identificateur contient après l'action du Create, l'adresse physique de l'objet).
    • Il en est de même pour la désallocation des objets de vos classes (leur destruction), celles-ci disposent d'un destructeur d'objets dénommé Destroy. Cette méthode de type destructor rend au processus, tout l'espace mémoire utilisé par l'objet physique, mais attention elle ne dé-référence pas l'identificateur, si nécessaire cette opération est à la charge du programmeur (grâce à l'instruction : identificateur:=nil).



     

    Le paramètre implicite self
    • Etant donnée une classe quelconque, chaque méthode de cette classe possède systématiquement dans son implémentation un paramètre implicite dénommé Self, que vous pouvez utiliser. 
    • Cet identificateur Self désigne l'objet (lorsqu'il sera instancié)dans lequel la méthode est appelée. 

     Exemple :

    Lors de l'instanciation (refA : = clA.Create ) la valeur 98752 de la référence, est passée automatiquement dans la variable implicite self de chaque méthode de l'objet refA, ce qui a pour résultat que dans la méthode F1, les expressions formelles self.a et self.x  prennent une valeur effective et désignent les valeurs effectives des champs a et x de l'objet n° 98752 du tas.
     

     

    3.7 Héritage

    Il s'agit en Delphi de l'héritage simple (graphe arborescent) dans lequel une famille dérive d'une seule classe de base.

    Syntaxe :

        Type
         classe_ancetre = class

          { champs }
          { méthodes }
         end;

         classe_fille = class(classe_ancetre)

          { champs }
          { méthodes }
         end;

        Var .....

    Voici une partie du graphe d'héritage de certaines classes de Delphi.

    La VCL de Delphi est entièrement construite par héritage (une hiérarchie objet complète).

    Comme nous l'avons signalé plus haut, il y a équivalence pour vos classes personnelles entre les deux écritures ci-dessous:
     
    Type
     ma_classe = class
       {déclarations de champs
       {spécification méthodes }
       {spécification propriétés }
     end;
    Type
     ma_classe = class(Tobject)
       {déclarations de champs
       {spécification méthodes }
       {spécification propriétés }
     end;

    L'écriture de gauche indique en fait que toutes les classes déclarées sans qualificatif d'héritage  héritent automatiquement de la classe TObject. La VCL de Delphi est entièrement construite par héritage (une hiérarchie objet complète) à partir de TObject.
     
     
    Outre la classe TObject, Delphi fournit le type de méta-classe (référence de classe) générale TClass :

    TClass = class of TObject;
     

    Ci-dessous la déclaration de la classe TObject dans Delphi :

    TObject = class
        constructor Create;
        procedure Free;
        class function InitInstance(Instance: Pointer): TObject;
        procedure CleanupInstance;
        function ClassType: TClass;
        class function ClassName: ShortString;
        class function ClassNameIs(const Name: string): Boolean;
        class function ClassParent: TClass;
        class function ClassInfo: Pointer;
        class function InstanceSize: Longint;
        class function InheritsFrom(AClass: TClass): Boolean;
        class function MethodAddress(const Name: ShortString): Pointer;
        class function MethodName(Address: Pointer): ShortString;
        function FieldAddress(const Name: ShortString): Pointer;
        function GetInterface(const IID: TGUID; out Obj): Boolean;
        class function GetInterfaceEntry(const IID: TGUID): PInterfaceEntry;
        class function GetInterfaceTable: PInterfaceTable;
        function SafeCallException(ExceptObject: TObject;
          ExceptAddr: Pointer): HResult; virtual;
        procedure AfterConstruction; virtual;
        procedure BeforeDestruction; virtual;
        procedure Dispatch(var Message); virtual;
        procedure DefaultHandler(var Message); virtual;
        class function NewInstance: TObject; virtual;
        procedure FreeInstance; virtual;
        destructor Destroy; virtual;
      end;
     

    La classe Tobject utilise ici la notion de méthode de classe :
     
    Une méthode de classe est une méthode (autre qu'un constructeur) qui agit sur des classes et pas sur des objets. 

    La définition d'une méthode de classe doit commencer par le mot réservé class.

    Par exemple dans Tobject :

    TObject = class
      ... 
      class function ClassName: ShortString;
      class function ClassParent: TClass; ...

    La déclaration de définition d'une méthode de classe doit également commencer par class : 

    class function TObject.ClassName: ShortString;
    begin
     //...le paramètre self est ici la classe Tobject
    end;
    etc...
     


     

    Par exemple dans Tobject :

    TObject = class
        constructor Create;
        procedure Free;
        class function InitInstance(Instance: Pointer): TObject;
        procedure CleanupInstance;
        function ClassType: TClass;
        class function ClassName: ShortString;
        class function ClassParent: TClass;
        etc...
     
      Modèle UML de toutes les classes de Delphi (ancêtre TObject) :



Signature d'une méthode

C'est son nom , le nombre et le type de ses paramètres


On dit qu'une méthode est surchargée dans sa classe si l'on peut trouver dans la classe plusieurs signatures différentes de la même méthode.

En Delphi, les en-têtes des méthodes surchargées de la même classe, doivent être suivies du qualifcateur overload afin d'indiquer au compilateur qu'il s'agit d'une autre signature de la même méthode.


Dans la classe classeA nous avons déclaré 3 surcharges de la même méthode Prix, qui pourrait évaluer un prix en fonction du montant rentré selon trois types de données exprimées en yen, en euro ou en dollar.   

Type
 classeA = class
        public
            function
Prix(x:yen) : real; overload;
            function Prix(x:euro): real; 
overload;
            function Prix(x:dollar) : real; 
overload;
 end;


Comment appeler une surcharge de méthode ?

Soit le programme de droite qui utilise la classeA définie à gauche avec 3 surcharges de la méthode Prix :

Unit Uclasses ;
interface

Type
yen = class … end;
euro = class … end;
dollar = class … end;
classeA = class
     public
            function Prix(x:yen) : real; overload;
            function Prix(x:euro): real; overload;
            function Prix(x:dollar) : real; overload;
 end;

implementation

function classeA.Prix(x:yen) : real; // signature n°1
begin
  …
end;

function classeA.Prix(x:euro): real;
begin
  …
end;

function classeA.Prix(x:dollar) : real;
begin
  …
end;
   
Program principal ;
Uses Uclasses ;

var refA : classeA;
       a : yen ; b : euro ; c : dollar ;

Procedure Calcul1 (ObjA : classeA; valeur : yen );
begin
  ObjA.Prix ( valeur)
end;

Procedure Calcul2 (ObjA : classeA;  valeur : euro);
begin
  ObjA.Prix ( valeur )
end;

Procedure Calcul3 (ObjA : classeA;  valeur : dollar);
Var ObjA : classeA ;
begin
  ObjA.Prix ( valeur)
end;
begin
 refA:= classeA.Create ;
 a := yen.Create ;
 b := euro.Create ;
 c := dollar.Create ;
 Calcul1 ( refA , a );
 Calcul2 ( refA , b );
 Calcul3 ( refA , c );
End.
Le compilateur connaît le type du second paramètre, lorsqu'il appelle :
ObjA.Prix ( valeur )

Il va alors chercher s'il existe une signature correspondant à ce type et va exécuter le code de la surcharge adéquate





4. Les propriétés en Delphi ...
 
 Une propriété définie dans une classe permet d'accéder à certaines informations contenues dans les objets instanciés à partir de cette classe. Une propriété a la même syntaxe de définition et d'utilisation que celle d'un champ d'objet (elle possède un type de déclaration), mais en fait elle peut invoquer une ou deux méthodes internes pour fonctionner ou se référer directement à un champ. Les méthodes internes sont déclarées à l'intérieur d'un bloc de défintion de la propriété.

Nous nous limiterons aux propriétés non tableau, car cette notion est commune à d'autres langages (C# en particulier).

    4.1. Définition

    Comme un champ, une propriété définit un attribut d'un objet.

    Mais un champ n'est qu'un emplacement de stockage dont le contenu peut être consulté et modifié, tandis qu'une propriété peut associer des actions spécifiques à la lecture et lors de la modification de ses données : une propriété peut être utilisée comme un attribut !
     
    Les propriétés proposent un moyen de contrôler l'accès aux attributs d'un objet et permettent le calcul des attributs. 

    Syntaxe :

    PROPERTY nomPropriété[indices] : type [index constanteEntière] spécificateurs ;

    Remarque :
    il faut au minimum un descripteur (ou spécificateur) d'accès pour une propriété :

    soit lecture seule (spécificateur READ),
    soit écriture seule (spécificateur WRITE),
    soit lecture et écriture.

    Attention :
    Une propriété ne peut pas être transmise comme paramètreréférence dans une procédure ou une méthode !

      Exemple de syntaxe d'écriture de propriétés :

     classeA = class
         public

            property prop1 : integer  read y write z ; // lecture et écriture
            property prop2 : char read F1 ; // lecture seule
            property prop3 : string write t ; // écriture seule
      end;


     

    4.2. Accès par read/write aux données d'une propriété

    Après les spécificateurs READ et WRITE, il faut préciser le moyen d'accès à la propriété : Ce moyen peut être un attribut, ou une méthode.
     

    Accès à une propriété par un attribut
     
    PROPERTY propriété1 READ Fpropriété1 ;

    La property propriété1 fait référence à l'attribut Fpropriété1 et en permet l'accès en lecture seule.

    Pour avoir un intérêt, la property propriété1 doit être déclarée en PUBLIC, tandis que l'attribut Fpropriété1 est déclaré en PRIVATE.

    Lorsque la propriété est définie, il faut que le champ auquel elle se réfère ait déjà été défini dans la classe, ou dans une classe ancêtre.
      
    Soit la classe classeA :

classeA = class
     public
       champ : integer ;
  end;


     
    Soit une autre version de la classe classeA :  

classeA = class
     private
        Fchamp : integer ;
     public
        property propr1 : integer read Fchamp write Fchamp ;
  end;



    On peut assimiler la propriété à une clef ouvrant une porte sur une information privée de l'objet.

     

    Exemple :

     
     

    Accès à une propriétés par une méthode

    Cas du spécificateur d'accès READ
    La méthode d'accès doit :
    • être une fonction,
    • être sans paramètres,
    • renvoyer le même type que celui de la propriété.

    Exemple :

     
     

    Cas du spécificateur d'accès WRITE
    La méthode d'accès doit :
    • être une procédure,
    • n'avoir qu'un seul paramètre formel passé par constante ou par valeur (pas par référence),
    • ce paramètre doit être de même type que celui de la propriété.

    Exemple :

     
     

    4.3. Propriétés tableaux (pour information)

    Ce ne sont pas des tableaux au sens strict du terme.

    Ce sont des structures (des listes) indicées permettant la manipulation d'un tableau virtuel accessible à travers un indice passé comme paramètre aux méthodes chargées de lire ou d'écrire la propriété.

    Exemple :

     
     

    4.4 Surcharge de propriétés

    Surcharge permettant d'augmenter la visibilité
    Lorsqu'une propriété est déclarée dans une classe, on peut la surcharger dans les classes dérivées en augmentant son niveau de visibilité.

MaClasse = class
     private
        Fchamp : integer ;
     protected
        property propr1 : integer read Fchamp
                                              write Fchamp ;
  end;
MaFille = class (MaClasse)
    private
        Fchamp : integer ;
     public
        property propr1 : integer read Fchamp
                                              write Fchamp ;
  end;


    Surcharge permettant de redéfinir un spécificateur existant
    On ne peut pas supprimer de spécificateur. Par contre on peut modifier le/les spécificateur(s) existant(s) ou ajouter le spécificateur manquant.

MaClasse = class
     private
        Fchamp : integer ;

     public
        property propr1 : integer read Fchamp ;
        property propr2 : integer read Fchamp ;
  end;

MaFille = class (MaClasse)
    private
        Fchamp : integer ;
        function lirePropr2 : integer ;
     public
        property propr1 : integer read Fchamp write Fchamp ;
        property propr2 : integer read lirePropr2 ;
                                  
  end;


    Redéfinition et masquage d'une propriété
    Une propriété redéfinie dans une classe remplace l'ancienne avec ses nouveaux attributs, son nouveau type.

MaClasse = class
     private
        Fchamp : integer ;
     protected
        property propr1 : integer read Fchamp
                                                  write Fchamp ;
  end;
MaFille = class (MaClasse)
    private
        FNom : string ;
     public
        property
propr1 : string  read Fnom ;

  end;

    Dés qu'une redéclaration de propriété contient une redéfinition de type, elle masque automatiquement la propriété parent héritée et la remplace entièrement. Elle doit donc être redéfinie avec au moins un spécificateur d'accès.