Classes
Les classes permettent de définir des objets, regroupant un membres, un membre étant soit un champ, une propriété, une méthode ou un opérateur de classe.
Il s'agit d'un type référence, alloué dynamiquement (à la différence des records), mettant en oeuvre des mécanismes d'abstraction et de virtualisation.
Les classes peuvent aussi être utilisées comme type d'implémentation d'une interface, et peuvent implémenter plusieurs interfaces.
Le type d'une classe est sa métaclasse.
Déclaration
Toutes les classes dérivent de la classe racine TObject, et peuvent être déclarées comme suit:
TNomDeLaClass = class (TClassAncetre)
...membres de la classe...
end;
Si la classe ancêtre n'est pas précisée, il s'agira automatiquement de TObject.
Des qualificatifs permettent d'indiquer les possibilité d'héritage d'une classe:
- class abstract désigne une classe abstraite, indépendamment de ses méthodes, qui devra être sous-classée avant de pouvoir être instanciée.
- class sealed désigne une classe scellée, qui ne pourra être sous-classée.
- class static désigne une classe statique, qui ne pourra être instantiée (Qubes 5.6).
- class partial désigne une classe partielle, dont la déclaration peut couvrir plusieurs unités et qui peut être librement étendue (Qubes 5.6).
Il est possible d'effectuer une déclaration avancée d'une classe avec
La classe devra alors être déclarée plus tard dans le code source.
Par convention, les classes sont préfixées d'un "T" suivi d'une majuscule, comme tous les autres types.
Les classes purement statiques et utilisées comme espace de nom ne prennent pas de majuscule.
Visibilité
Il existe quatre classes de visibilité:
- private : les membres privés ne sont visible que depuis la classe où ils sont déclarés.
- protected : les membres protégés sont visible dans la classe où ils sont déclarés, et toutes ses sous classes, mais ne sont pas visible depuis l'extérieur.
- public : les membres publiques sont accessibles depuis tout le code.
- published : les membres publiés sont accessibles depuis tout le code, et seront de plus exposés en dehors du script, notamment dans des couches d'appel RPC, JSON et autres flux de persistence.
Depuis Qubes 6.1, dans le cas d'une unité, les membres privés et protégés sont accessibles pour toutes les méthodes de l'unité où ils sont déclarés.
Champs
Les champs se déclarent simplement par
Par convention, les noms de champs sont préfixés par un "F" suivi d'une majuscule.
Il est possible d'affecter une valeur par défaut à un champ.
La declaration peut être suivi du qualificateur "readonly;" (Qubes 10.0), le champ ne sera alors assignable qu'à la déclaration ou dans le corps d'un constructeur de la classe.
Méthodes
Les méthodes peuvent être des function (retourne un résulat), procedure (ne retourne pas un résultat) ou method (retourne optionnellement un résultat).
Méthodes d'instances
Elles peuvent être des méthodes d'instance, qui opéreront sur une instance de l'objet:
TMaClasse = class
function NomMethode(parametre : Integer) : TypeResultat; // déclaration
end;
function TMaClasse.NomMethode(parametre : Integer) : TypeResultat; // implémentation
begin
...
end;
variable := monInstance.NomMethode(parametre); // utilisation
Au sein d'une méthode, la pseudo-variable Self donne accès à l'instance sur laquelle elle a été appelée.
Méthodes de classe
Il existe aussi des méthodes de classe, qui opéreront sur une référence de classe:
TMaClasse = class
class function NomMethode(parametre : Integer) : TypeResultat; // déclaration
end;
class function TMaClasse.NomMethode(parametre : Integer) : TypeResultat; // implémentation
begin
...
end;
variable := TMaClasse.NomMethode(parametre); // utilisation
Il est possible d'appeler une méthode de classe sur une classe explicite, sur une métaclasse ou sur une instance (qui sera interprétée en fonction de sa classe).
Au sein d'une méthode de classe, la pseudo-variable Self donne accès à la méta-classe sur laquelle elle a été appelée.
Méthodes virtuelles
Méthodes de classe et méthodes d'instance sont par défaut non-virtuelles. Il est possible de les définir comme virtuelles avec le qualificateur virtual, et de les surcharger avec override.
TAncetre = class
procedure MaProcedure; virtual;
end;
type
TEnfant = class (TAncetre)
procedure MaProcedure; override;
end;
Qualificateurs de méthodes
Le qualificatif abstract permet d'indiquer qu'une méthode virtuelle est abstraite (ceci rend la classe abstraite).
Le qualificatif empty permet d'indiquer que l'implémentation est vide (utile lors de prototypages, Qubes 5.6).
Le qualificatif static peut être appliqué à une méthode de classe, il indique que cette méthode utilise la classe comme espace de nom, mais ne dépend ni d'une instance ni d'une référence de classe. Dans une méthode statique, Self n'est pas définie.
Constructeurs
Les constructeurs sont des méthodes spéciales dont le role est de créer une nouvelle instance, et de l'initialiser.
Le constructeur par défaut est "Create", il est possible de créer des constructeurs supplémentaires de nom quelconque, en nombre non limité. Les constructeurs peuvent aussi être virtuels, et invoqués par le biais d'une méta-classe.
Pour chaque classe il est possible de spécifier un constructeur par défaut en le qualifiant par "default;", si aucun constructeur n'est explicitement spécifié comme étant un constructeur par défaut, le constructeur nommé "Create" sera utilisé s'il est défini. A défaut, le constructeur par défaut de la classe parente sera utilisé (et ce récursivement jusqu'à TObject).
Une instance peut être créé en appelant un constructeur nommé, ou au travers de l'opérateur new, qui appelera le constructeur par défaut.
obj2 := new TUneClasse(param1, param2);
Il est possible d'invoquer un constructeur sur un nom de classe, ou une référence de classe (méta-classe).
Destructeurs
La gestion de la mémoire étant automatisée, les destructeurs sont utilisés pour marquer une référence d'objet comme ayant été détruite, elle ne pourra alors plus être utilisée sans déclencher d'erreur.
Le destructeur par défaut est nommé "Destroy" et est virtuel.
Il existe une méthode "Free" qui appelle "Destroy" si la référence est non-nil et si l'objet n'a pas déjà été détruit. Dans le cas contraire, "Free" ne fait rien et ne déclenche pas d'erreur.
Propriétés
Les propriétés permettent d'exposer des propriétés d'un objet de manière encapsulée, elles peuvent être simples ou paramétrées:
property ProprieteParametree[parametres...] : TypePropriete [read Getter] [write Setter] [; default];
Une proprieté à un Getter et un Setter optionels (au moins une des deux doit être définie). Une propriété ne définissant qu'un Getter sera dite en lecture seule, une propriété ne définissant qu'un Setter sera dit en écriture seule, et une propriété définissant les deux sera dite en lecture écriture.
Pour une propriété simple, le Getter peut être un champ, pour un accès direct en lecture, ou une méthode sans paramètre retournant un résultat du type approprié. Le Setter quand à lui peut être un champ, pour un accès direct en écriture, ou une méthode acceptant un paramètre unique du type de la propriété et ne retournant pas de résultat.
Pour une propriété paramétrée, les Getter/Setter devront être des méthodes, similairement à la propriété simple, mais acceptant les paramètres de la propriété. Une propriété paramétrée pourra être qualifiée avec default, dans ce cas elle sera accessible directement sur une instance de l'objet.
La propriété par défaut est unique pour chaque classe. Cette propriété est de type tableau, et peut être accédée plus rapidement par le nom de l'objet suivi de crochets contenant l'indice.
Par exemple, les deux lignes suivantes sont équivalentes :
TIntegerList[0] // la propriété Integers est la propriété par défaut de la classe TIntegerList
Il est aussi possible de définir un index constant pour une propriété nommée, par exemple sur TIntegerList on aurait pu défini une propriété Integer10 accédant directement à l'élément d'index 10 :
// les deux expression seraient alors équivalentes
v := list.Integers[0];
v := list.FirstInteger;
A partir de la version Qubes 6.1 il est aussi possible d'utiliser des expressions pour définir les Getter & Setter, celles-ci doivent être délimitées par des parenthèses.
Pour les expressions de Setter, la valeur affectée est accessible au travers de l'identificateur Value, elle peuvent correspondre soit à la partie gauche d'une affectation, soit à une instruction.
property Degres: Float read (RadToDeg(FRadians)) write (FRadians:=DegToRad(Value));
Il est aussi possible de faire référence à une autre propriété par son nom (sans parenthèses), ce qui définira un alias pour le Setter ou le Getter.
Dans le cas d'une propriété exposant un champ en direct, il est possible d'utiliser une déclaration courte avec champ implicite
Déclare une propriété de type chaîne, accessible en lecture écriture et stockée dans un champ anonyme. L’intérêt de ce genre de déclaration par rapport à un simple champ est de permettre l'ajout ultérieur de getter/setter sans entraîner d'incompatibilité (un champ peut être passé par référence, alors qu'une propriété ne peux pas l'être).
A partir de Qubes 10.0 une propriété peut être marquée reintroduce. Cela permet de couvrir le cas ou une méthode est mis à niveau en une propriété de même nom, tout en supportant le code existant dans lequel des appels à cette méthode sont présent dans le code existant avec des parenthèses vide cosmétique (). Sans reintroduce, les parenthèse forcent un appel de méthode, qui échouera avec une erreur de compilation. Avec reintroduce, l'appel sera ignoré et un simple Hint sera signalé. A noter que cela rend ambigüe le cas d'une propriété retournant un pointeur vers une fonction, pour lequel les () indiquent un appel à la fonction retournée.
Opérateurs
- as permet de transtyper une instance d'une classe ancêtre vers une sous-classe.
- is permet de tester de manière dynamique si une classe hérite d'un ancêtre donné.
- new permet d'instancier une classe en invoquant son constructeur par défaut.
Opérateurs de classe
Il est possible de définir une surcharge des opérateurs d'affectation composites pour une classe, il s'agit de +=, -=, *=, etc.
La méthode associée à l'opérateur devant accepter une paramètre unique du type de l'opérande. Avec la définition ci-dessus, les deux lignes ci-dessous seront équivalentes:
monInstance.NomMethode( valeur );
Il s'agit donc d'un raccourci syntaxique.