1. Description générale de Delphi
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.
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 :
|
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...). |
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 :
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.
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. |
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 ?
|
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.
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.
|
Le paramètre implicite self
|
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.
Rappelons que pratiquement,
en POO l'encapsulation permet de masquer les informations et les opérations
d'un objet aux autres objets. Contrairement à certains autres langages
orientés objet, dans Delphi par défaut, s’il n’y a pas de
descripteur d’encapsulation, tout est visible (donc public).
Delphi possède au moins
quatre niveaux d'encapsulation des informations
dans un objet qui sont matérialisés par des descripteurs :
published : Les informations sont accessibles par toutes les instances de toutes les classes (les clients) + accessibles à l'inspecteur d'objet de Delphi. |
public : Les informations sont accessibles par toutes les instances de toutes les classes (les clients). |
protected : Les informations ne sont accessibles qu'à toutes les instances de la classe elle-même et à tous celles qui en héritent (ses descendants). |
private : Les informations ne sont accessibles qu'à toutes les instances de la classe elle-même. |
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
classe_fille = class(classe_ancetre)
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
La déclaration de définition d'une méthode de classe doit également commencer par class : class function
TObject.ClassName: ShortString; |
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
- L'appel ObjA.Prix(valeur) dans Calcul1( refA , a ) sur a de type yen provoque la recherche de la signature suivante Prix ( type yen), c'est la signature n°1 qui est trouvée et exécutée.
- L'appel ObjA.Prix(valeur) dans Calcul2( refA , b ) sur a de type euro provoque la recherche de la signature suivante Prix ( type euro) ), c'est la signature n° 2 qui est trouvée et exécutée.
- L'appel ObjA.Prix(valeur) dans Calcul3( refA , c ) sur a de type dollar provoque la recherche de la signature suivante Prix ( type dollar) ), c'est la signature n°3 qui est trouvée et exécutée.
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).
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),
|
Attention :
Une propriété ne peut pas être transmise comme paramètreréférence dans une procédure ou une méthode ! |
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 :
|
Exemple :
Cas du spécificateur d'accès WRITE
La méthode
d'accès doit :
|
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 :
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. |