Methodes (Classes Pascal Delphi)

Une méthode est une procédure ou une fonction associée à la classe. Un appel de méthode spécifie l'objet (ou la classe si c'est une méthode de classe) sur lequel la méthode agit. Par exemple, SomeObject.Free appelle la méthode Free de SomeObject.
Cette rubrique couvre les sujets suivants :
  • Déclarations et implémentations des méthodes
  • Liaison de méthode
  • Surcharge de méthodes
  • Constructeurs et destructeurs
  • Méthodes de messages
A propos des méthodes
A l'intérieur d'une déclaration de classe, une méthode apparaît comme un en-tête de procédure ou de fonction qui fonctionne comme une déclaration avancée (forward). Quelque part après la déclaration de classe, mais à l'intérieur du même module, chaque méthode doit être implémentée par une déclaration de définition. Si par exemple la déclaration de TMyClass contient une méthode appelée DoSomething :
type    TMyClass = class(TObject)

...
procedure DoSomething;
...
end;

La déclaration de définition de DoSomething doit se trouver après dans le même module :
procedure TMyClass.DoSomething;

begin
...
end;
 
Si une classe peut être déclarée dans la section interface ou dans la section implémentation d'une unité, les déclarations de définition des méthodes d'une classe doivent se trouver dans la section implémentation.
Dans l'en-tête d'une déclaration de définition, le nom de la méthode est toujours qualifié par le nom de la classe à laquelle elle appartient. L'en-tête peut répéter la liste de paramètres de la déclaration de classe ; si c'est le cas, l'ordre, le type et le nom des paramètres doivent correspondre exactement et, si la méthode est une fonction, cet impératif vaut aussi pour le type de la valeur renvoyée.
Les déclarations des méthodes peuvent inclure des directives spéciales qui ne sont pas utilisées par d'autres fonctions ou procédures. Les directives doivent apparaître uniquement dans la déclaration de classe et pas dans la déclaration de définition, et doivent toujours être dans l'ordre suivant :

reintroduce; overload; liaison; convention d'appel; abstract; avertissement

liaison est virtual, dynamic ou override ; convention d'appel est register, pascal, cdecl, stdcall ou safecall ; et avertissement est platform, deprecated ou library.

Inherited
Le mot réservé inherited joue un rôle particulier dans l'implémentation de comportements polymorphiques. Il peut apparaître dans une définition de méthode avec ou sans identificateur à la suite.
Si inherited est suivi par le nom d'un membre, il représente un appel de méthode normal ou une référence à une propriété ou à un champ, sauf que la recherche du membre référencé commence dans l'ancêtre immédiat de la classe de la méthode. Ainsi, quand l'instruction :
inherited Create(...);
se produit dans la définition d'une méthode, elle appelle la méthode Create héritée.
Quand inherited est utilisé sans être suivi d'un identificateur, il désigne la méthode héritée portant le même nom que la méthode en cours ou, si la méthode en cours est un gestionnaire de message, le gestionnaire de message hérité pour le même message. Dans ce cas, inherited ne prend pas de paramètre explicite, mais transmet à la méthode héritée les paramètres utilisés pour l'appel de la méthode en cours. Par exemple :
inherited;
apparaît fréquemment dans l'implémentation des constructeurs. Cette instruction appelle le constructeur hérité en lui transmettant les mêmes paramètres que ceux transmis au descendant.
Self
A l'intérieur de l'implémentation d'une méthode, l'identificateur Self désigne l'objet dans lequel la méthode est appelée. Voici, par exemple, l'implémentation de la méthode Add de TCollection dans l'unité Classes.
function TCollection.Add: TCollectionItem;

begin
Result := FItemClass.Create(Self);
end;
La méthode Add appelle la méthode Create de la classe référencée par le champ FItemClass qui est toujours un descendant de TCollectionItem. TCollectionItem.Create ne prend qu'un seul paramètre de type TCollection, donc Add le transmet à l'objet instance de TCollection d'où Add est appelée. Cela est illustré par le code suivant :
var MaCollection: TCollection;

...
MyCollection.Add // MyCollection est transmis à la méthode TCollectionItem.Create
Self est utilisé dans divers cas de figure. Par exemple, un identificateur de membre déclaré dans un type classe peut être redéclaré dans le bloc de l'une des méthodes de la classe. Dans ce cas, vous pouvez accéder à l'identificateur du membre d'origine en utilisant Self.Identificateur.

Liaison de méthode
Les liaisons de méthode peuvent être statiques (c'est le cas par défaut), virtuelles ou dynamiques. Les méthodes virtuelles et dynamiques peuvent être redéfinies et elles peuvent être abstraites. Ces éléments jouent un rôle quand une variable d'un type classe contient une valeur d'un type classe descendant. Ils déterminent quelle implémentation est activée lors de l'appel d'une méthode.

Méthodes statiques

Par défaut les méthodes sont statiques. Quand une méthode statique est appelée, le type déclaré (à la compilation) de la variable classe ou objet est utilisé dans l'appel de méthode pour déterminer l'implémentation à activer. Dans l'exemple suivant, les méthodes Draw sont statiques :
type     TFigure = class

procedure Draw;
end;
TRectangle = class(TFigure)
procedure Draw;
end;
Etant donné ces déclarations, le code suivant illustre l'effet de l'appel d'une méthode statique. Dans le second appel de Figure.Draw, la variable Figure désigne un objet de la classe TRectangle, mais l'appel utilise l'implémentation de Draw dans TFigure, car le type déclaré de la variable Figure est TFigure.
var     Figure: TFigure;    

Rectangle: TRectangle;
begin
Figure := TFigure.Create;
Figure.Draw; // appelle TFigure.Draw
Figure.Destroy; Figure := TRectangle.Create;
Figure.Draw; // appelle TFigure.Draw
TRectangle(Figure).Draw; // appelle TRectangle.Draw
Figure.Destroy; Rectangle := TRectangle.Create;
Rectangle.Draw; // appelle TRectangle.Draw
Rectangle.Destroy;
end;
 
Méthodes virtuelles et dynamiques
Pour définir une méthode comme étant virtuelle ou dynamique, incluez la directive virtual ou dynamic dans la déclaration. Les méthodes dynamiques ou virtuelles, à la différence des méthodes statiques, peuvent être redéfinies dans les classes dérivées. Lorsqu'une méthode redéfinie est appelée, c'est le type réel (à l'exécution) de la classe ou de l'objet utilisé dans l'appel de la méthode, et non pas le type déclaré de la variable, qui détermine l'implémentation activée.
Pour redéfinir une méthode, redéclarez-la avec la directive override. Une déclaration override doit correspondre à la déclaration de l'ancêtre dans l'ordre et le type des paramètres, ainsi que dans le type éventuel du résultat.
Dans l'exemple suivant, la méthode Draw déclarée dans TFigure est redéfinie dans deux classes dérivées :
type

TFigure = class
procedure Draw; virtual;
end;
TRectangle = class(TFigure)
procedure Draw; override;
end;
TEllipse = class(TFigure)
procedure Draw; override;
end;
Etant donné ces déclarations, le code suivant illustre le résultat de l'appel d'une méthode virtuelle via une variable dont le type réel change à l'exécution :
var    Figure: TFigure;    

begin
Figure := TRectangle.Create;
Figure.Draw; // appelle TRectangle.Draw
Figure.Destroy; Figure := TEllipse.Create;
Figure.Draw; // appelle TEllipse.Draw
Figure.Destroy;
end;
Seules les méthodes virtuelles et dynamiques peuvent être redéfinies. Par contre, toutes les méthodes peuvent être surchargées ;
Le compilateur Delphi prend également en charge le concept de méthode virtuelle final. Lorsque le mot-clé final est appliqué à une méthode virtuelle, aucune classe descendante ne peut redéfinir cette méthode. L'usage du mot clé final est une décision de conception importante qui permet de définir l'utilisation de la classe. Il peut aussi donner au compilateur des conseils lui permettant d'optimiser le code produit.

Comparaison des méthodes virtuelles et des méthodes dynamiques
Dans Delphi pour Win32, d'un point de vue sémantique, les méthodes virtuelles et les méthodes dynamiques sont équivalentes. Toutefois, elles diffèrent dans l'implémentation de la répartition de l'appel de méthode à l'exécution : les méthodes virtuelles optimisent la rapidité alors que les méthodes dynamiques optimisent la taille du code.

En général, les méthodes virtuelles constituent la manière la plus efficace d'implémenter un comportement polymorphique. Les méthodes dynamiques sont utiles quand une classe de base déclare de nombreuses méthodes pouvant être redéfinies qui sont héritées par de nombreuses classes dérivées d'une application mais rarement redéfinies.
Remarque: N'utilisez les méthodes dynamiques que s'il existe un avantage clair et observable. Utilisez habituellement les méthodes virtuelles.

Redéfinition ou masque
Si une déclaration de méthode spécifie le même identificateur de méthode et la même signature de paramètres qu'une méthode héritée sans spécifier la directive override, la nouvelle déclaration masque simplement la méthode héritée sans la redéfinir. Les deux méthodes existent alors dans la classe dérivée où le nom de méthode est lié statiquement. Par exemple :
type   

T1 = class(TObject)
procedure Act; virtual;
end;
T2 = class(T1)
procedure Act; // Act est redéclarée mais pas redéfinie
end;
var SomeObject: T1;
begin
SomeObject := T2.Create;
SomeObject.Act; // appelle T1.Act
end;
Reintroduce
La directive reintroduce supprime les avertissements du compilateur informant qu'une méthode virtuelle précédemment déclarée est masquée. Par exemple,
procedure DoSomething; reintroduce;   // la classe ancêtre a aussi une méthode DoSomething

Utilisez reintroduce quand vous voulez masquer une méthode virtuelle héritée avec une nouvelle méthode.
Méthodes abstraites
Une méthode abstraite est une méthode virtuelle ou dynamique n'ayant pas d'implémentation dans la classe où elle est déclarée. Son implémentation est déléguée à une classe dérivée. Les méthodes abstraites doivent être déclarées en spécifiant la directive abstract après virtual ou dynamic. Par exemple :
procedure DoSomething; virtual; abstract;
Vous ne pouvez appeler une méthode abstraite que dans une classe ou une instance de classe dans laquelle la méthode a été redéfinie.

Méthodes de classe
La plupart des méthodes sont appelées méthodes d'instance, car elles opèrent sur une instance individuelle d'un objet. Une méthode de classe est une méthode (autre qu'un constructeur) qui agit sur des classes et pas sur des objets. Il existe deux types de méthodes de classe : les méthodes de classe ordinaires et les méthodes statiques de classe.

Méthodes de classe ordinaires
La définition d'une méthode de classe doit commencer par le mot réservé class. Par exemple :
type   TFigure = class  

public
class function Supports(Operation: string): Boolean; virtual;
class procedure GetInfo(var Info: TFigureInfo); virtual;
...
end;
La déclaration de définition d'une méthode de classe doit également commencer par class. Par exemple :
class procedure TFigure.ObtenirInfos(var Info: TFigureInfo);

begin
...
end;
Dans la déclaration de définition d'une méthode de classe, l'identificateur Self représente la classe où la méthode est appelée (ce peut être un descendant de la classe dans laquelle elle est définie). Si la méthode est appelée dans la classe C, alors Self est de type class of C. Vous ne pouvez donc pas utiliser Self pour accéder à des champs d'instance, des propriétés d'instance et des méthodes normales (objet). Par contre, vous pouvez l'utiliser pour appeler les constructeurs ou d'autres méthodes de classe ou pour accéder aux propriétés de classe et aux champs de classe.
Une méthode de classe peut être appelée via une référence de classe ou une référence d'objet. Quand elle est appelée via une référence d'objet, la classe de l'objet devient la valeur de Self.

Méthodes statiques de classe

Comme les méthodes de classe, les méthodes statiques de classe peuvent être accessibles sans une référence d'objet. A la différence des méthodes de classe ordinaires, les méthodes statiques de classe n'ont pas de paramètre Self. Elles ne peuvent pas accéder aux membres d'instance. (Elles peuvent toujours accéder aux champs de classe, aux propriétés de classe et aux méthodes de classe.) En outre, contrairement aux méthodes de classe, les méthodes statiques de classe ne peuvent pas être déclarées virtual
Pour rendre une méthode de classe statique, ajoutez le mot static à leur déclaration, par exemple :
type  

TMyClass = class
strict private
class var
FX : Integer;
strict protected
// Remarque : les accesseurs des propriétés de classe doivent être déclarés comme méthodes de classe statiques.
class function GetX: Integer; static;
class procedure SetX(val: Integer); static;
public
class property X: Integer read GetX write SetX;
class procedure StatProc(s: String); static;
end;

Comme pour une méthode de classe, vous pouvez appeler une méthode statique de classe par l'intermédiaire du type de la classe (c'est-à-dire sans référence d'objet), par exemple:
TMyClass.X := 17;

TMyClass.StatProc('Hello');
Surcharge de méthodes
Une méthode peut être redéclarée en utilisant la directive overload. Dans ce cas, si la méthode redéclarée a une signature de paramètres différente de celle de son ancêtre, elle surcharge la méthode héritée sans la cacher. L'appel de la méthode dans une classe dérivée active l'implémentation qui correspond aux paramètres utilisés dans l'appel.
Si vous surchargez une méthode virtuelle, utilisez la directive reintroduce quand vous la redéclarez dans les classes dérivées. Par exemple :
type  

T1 = class(TObject)
procedure Test(I: Integer); overload; virtual;
end;
T2 = class(T1)
procedure Test(S: string); reintroduce; overload;
end;
...
SomeObject := T2.Create;
SomeObject.Test('Hello!'); // appelle T2.Test
SomeObject.Test(7); // appelle T1.Test

A l'intérieur d'une classe, vous ne pouvez pas publier de multiples méthodes surchargées avec le même nom. La maintenance des informations de type à l'exécution requiert un nom unique pour chaque membre publié.
type    

TSomeClass = class
published
function Func(P: Integer): Integer;
function Func(P: Boolean): Integer; // erreur
...
Les méthodes qui servent de spécificateurs de lecture ou d'écriture de propriétés ne peuvent pas être surchargées.
L'implémentation d'une méthode surchargée doit répéter la liste des paramètres spécifiée dans la liste de paramètres de la déclaration de classe.

Constructeurs
Un constructeur est une méthode spéciale qui crée et initialise des instances d'objet. La déclaration d'un constructeur ressemble à celle d'une procédure en commençant par le mot constructor. Exemples :
constructor Create;

constructor Create(AOwner: TComponent);
Les constructeurs doivent utiliser la convention d'appel register par défaut. Bien que la déclaration ne spécifie pas de valeur renvoyée, un constructeur renvoie une référence à l'objet qu'il a créé ou qui est appelé.
Une classe peut avoir plusieurs constructeurs mais doit en avoir au moins un. L'habitude veut qu'on appelle le constructeur Create.
Pour créer un objet, appelez la méthode constructeur sur un type classe. Par exemple :
MyObject := TMyClass.Create;
Cette instruction alloue le stockage pour le nouvel objet, initialise la valeur de tous les champs scalaires à zéro, affecte nil à tous les champs de type pointeur ou de type classe, et une chaîne vide à tous les champs chaîne. Les autres actions spécifiées dans l'implémentation du constructeur sont effectuées ensuite. Généralement, les objets sont initialisés en fonction des valeurs transmises comme paramètres au constructeur. Enfin, le constructeur renvoie une référence à l'objet qui vient d'être créé et initialisé. Le type de valeur renvoyée est le même que celui du type classe spécifié dans l'appel du constructeur.

Si une exception est déclenchée lors de l'exécution d'un constructeur appelé dans une référence de classe, le destructeur Destroy est appelé automatiquement pour détruire l'objet inachevé.
Quand un constructeur est appelé en utilisant une référence d'objet (au lieu d'une référence de classe), il ne crée pas d'objet. Le constructeur agit à la place sur l'objet spécifié en n'exécutant que les instructions de l'implémentation du constructeur et renvoie ensuite une référence à l'objet. Un constructeur est généralement appelé dans une référence d'objet en conjonction avec le mot réservé inherited afin d'exécuter un constructeur hérité.

Voici un exemple de type classe et de son constructeur.
type   

TShape = class(TGraphicControl)
private
FPen: TPen;
FBrush: TBrush;
procedure PenChanged(Sender: TObject);
procedure BrushChanged(Sender: TObject);
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
...
end;

constructor TShape.Create(Owner: TComponent);
begin
inherited Create(Owner); // Initialise les parties héritées
Width := 65; // Modifie les propriétés héritées
Height := 65;
FPen := TPen.Create; // Initialise les nouveaux champs
FPen.OnChange := PenChanged;
FBrush := TBrush.Create;
FBrush.OnChange := BrushChanged;
end;
 
Généralement, la première action d'un constructeur est d'appeler le constructeur hérité afin d'initialiser les champs hérités de l'objet. Le constructeur initialise ensuite les champs introduits dans la classe dérivée. Comme un constructeur efface toujours le stockage alloué à un nouvel objet, tous les champs contiennent au départ zéro (pour les types scalaires), nil (types pointeur et classe), chaîne vide (types chaîne) ou Unassigned (variants). Il n'est donc pas nécessaire que l'implémentation du constructeur initialise les champs sauf ceux devant contenir une valeur non nulle ou non vide.

Quand il est appelé via un identificateur de type classe, un constructeur déclaré comme virtual est équivalent à un constructeur statique. Quand ils sont combinés avec des types référence de classe, les constructeurs virtuels permettent une construction polymorphique des objets : c'est-à-dire la construction d'objets dont le type est inconnu à la compilation.

Destructeurs
Un destructeur est une méthode spéciale qui détruit l'objet à l'endroit de son appel et libère sa mémoire. La déclaration d'un destructeur ressemble à celle d'une procédure mais elle commence par le mot destructor. Exemple :
destructor SpecialDestructor(SaveData: Boolean);

destructor Destroy; override;
Les destructeurs sous Win32 doivent utiliser la convention d'appel register par défaut. Même si une classe peut avoir plusieurs destructeurs, il est conseillé que chaque classe redéfinisse la méthode Destroy héritée et ne déclare pas d'autres destructeurs.
Pour appeler un destructeur, il faut référencer une instance d'objet. Par exemple :
MyObject.Destroy;
Lors de l'appel d'un destructeur, les actions spécifiées dans l'implémentation du destructeur sont d'abord exécutées. Généralement, cela consiste à détruire les objets incorporés et libérer les ressources allouées par l'objet. Ensuite, le stockage alloué à l'objet est libéré.
Voici un exemple d'implémentation d'un destructeur :
destructor TShape.Destroy;

begin
FBrush.Free;
FPen.Free;
inherited Destroy;
end;
 
Généralement, la dernière action de l'implémentation d'un destructeur est l'appel du destructeur hérité afin de détruire les champs hérités de l'objet.
Quand une exception est déclenchée lors de la création d'un objet, Destroy est appelée automatiquement afin de libérer l'objet inachevé. Cela signifie que Destroy doit être capable de libérer des objets partiellement construits. Comme un constructeur commence par initialiser à zéro ou à des valeurs vides les champs d'un nouvel objet avant d'effectuer d'autres actions, les champs de type classe ou pointeur d'un objet partiellement construit ont toujours la valeur nil. Un destructeur doit donc tester les valeurs nil avant d'agir sur des champs de type classe ou pointeur. L'appel de la méthode Free (définie dans TObject) au lieu de Destroy permet de tester facilement les valeurs nil avant de détruire un objet.

Méthodes de messages
Les méthodes de messages implémentent des réponses à des messages répartis dynamiquement. La syntaxe des méthodes de messages est supportée sur toutes les plates-formes. VCL utilise des méthodes de messages pour répondre aux messages Windows.
Une méthode de message est créée en incluant la directive message dans une déclaration de méthode, suivie d'une constante entière comprise entre 1 et 49151 qui spécifie le numéro d'identification du message. Pour les méthodes de messages des contrôles VCL, la constante entière peut être un des numéros d'identification de message Win32, avec les types d'enregistrements correspondants, dans l'unité Messages. Une méthode de message doit être une procédure qui n'attend qu'un seul paramètre var.
Par exemple :
type    

TTextBox = class(TCustomControl)
private
procedure WMChar(var Message: TWMChar); message WM_CHAR;
...
end;
 
La déclaration d'une méthode de message n'a pas besoin de spécifier la directive override pour redéfinir une méthode de message héritée. En fait, il n'est même pas nécessaire qu'elle porte le même nom de méthode ou qu'elle utilise les mêmes types de paramètres que la méthode qu'elle redéfinit. C'est le numéro d'identification du message seul qui détermine les messages auxquels la méthode répond et la méthode qu'elle redéfinit.
Implémentation des méthodes de messages
L'implémentation d'une méthode de message peut appeler la méthode de message héritée, comme dans l'exemple suivant :
procedure TTextBox.WMChar(var Message: TWMChar);

begin
if Message.CharCode = Ord(#13) then
ProcessEnter
else
inherited;
end;
 
L'instruction inherited recherche en arrière dans la hiérarchie des classes la première méthode de message ayant le même numéro d'identification que la méthode en cours et lui transmet automatiquement l'enregistrement message. Si aucune classe ancêtre n'implémente de méthode de message pour le numéro d'identification spécifié, inherited appelle la méthode DefaultHandler définie initialement dans TObject.
L'implémentation de DefaultHandler dans TObject rend le contrôle sans rien faire. En redéfinissant DefaultHandler, une classe peut implémenter sa propre gestion par défaut des messages. Sous Win32, la méthode DefaultHandler pour les contrôles appelle DefWindowProc de l'API Win32.

Répartition des messages
Les gestionnaires de messages sont rarement appelés directement. Les messages sont répartis à un objet en utilisant la méthode Dispatch héritée de TObject :
procedure Dispatch(var Message);
Le paramètre Message transmis à Dispatch doit être un enregistrement dont la première entrée est un champ de type Word contenant le numéro d'identification du message.
La méthode Dispatch recherche en arrière dans la hiérarchie des classes, en commençant par la classe de l'objet où elle est appelée, et appelle la première méthode de message pour le numéro d'identification qui lui a été transmis. Si aucune méthode de message ne correspond au numéro d'identification transmis, Dispatch appelle DefaultHandler.

Aucun commentaire: