Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS DELPHI F.A.Q DELPHI TUTORIELS DELPHI LIVRES COMPOSANTS SOURCES DEFI TELECHARGEZ DELPHI TV

Les références de classe ou métaclasses

29/07/2004

Par Laurent Dardenne (home page)
 

Cet article est issue d'une discussion sur les métaclasses sur le forum de la FAQ Delphi de Developpez.com.

Les méthode de classes appelée parfois métaclasse et les références de classe sont des éléments du langage important sous Delphi. L'aide en ligne n'étant pas trés prolixe sur le sujet ( bien que de nombreux extraits émaillent mes propos) et n'ayant trouvé sur le net que des bribes d'information, cet article vous propose d'approfondir le sujet, enfin je l'espére.

Merci à Ptitbob, Nono40, Pierre Castelain, Hachesse, Bloon et Delphiprog.


1. Public concerné
2. Définitions
2.1. Classe
2.2. Méthode
2.3. Objet, instance ou instance de classe
2.4. Variable ou référence d'objet
3. Les méthodes abstraites
3.1. Exception EAbstractError
4. Les prédéclarations de classes
5. Les pointeurs de méthode d'objet
6. Les références de classe ou métaclasses
6.1. Affectation et variable de classe
6.2. Constructeur virtuel
6.3. Le polymorphisme
6.4. Etude de cas
6.4.1. Gestion des 3 classes souhaitées
6.4.2. Ajout de la classe TObject
6.4.3. Ajout d'une classe ascendante
7. Les méthodes de classe
7.1. Le pattern Singleton
7.2. Retrouver une classe par son nom


1. Public concerné


Débutant Avancé Expert
[ ]___[ ] [X]___[X] [ ]___[ ]
Testé avec Delphi 5 sous XP pro.


2. Définitions



2.1. Classe


Une classe (un type classe) définit une structure composée de champs, de méthodes et de propriétés. Les champs, méthodes et propriétés d'une classe sont appelés ses composants ou ses membres.

type NomDeClasse = class (ClasseAncêtre) ListeMembre end;


2.2. Méthode


Une méthode est une procédure ou une fonction associée à une classe.


2.3. Objet, instance ou instance de classe


Les objets sont des blocs de mémoire alloués dynamiquement dont la structure est déterminée par leur type de classe. Chaque objet détient une copie unique de chaque champ défini dans la classe. Par contre, toutes les instances d'une classe partagent les mêmes méthodes. Les instances d'un type classe, c'est à dire les instances de classe, sont appelées des objets.


2.4. Variable ou référence d'objet


Une variable de type classe est en fait un pointeur qui référence le bloc mémoire alloué pour un objet. Plusieurs variables peuvent donc désigner le même objet. Comme les autres pointeurs, les variables de type classe peuvent contenir la valeur nil, dans ce cas elle ne fait donc aucune référence à un objet.

Une variable de type TObject contient une instance de la classe TObject.

Note : Un constructeur renvoie une référence d'objet.


3. Les méthodes abstraites


Abstrait
"Qui use d'abstractions, opére sur des qualités et des relations et non sur la réalité."

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 FaireQuelquechose; 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é surchargée ( mot-clé override ).
Voir par exemple l'utilisation d'abstract dans le source suivant livré avec Delphi
..\Delphi\Demos\Threads\SortThds.pas


3.1. Exception EAbstractError


C'est la classe des exceptions pour les appels de méthodes abstraites, déclarée dans l'unité sysutils.pas

... EAbstractError = class(Exception); ...
EAbstractError est déclenchée quand une application tente d'appeler une méthode abstraite. Elle est également déclenchée à la conception quand un composant contenant une méthode abstraite est placé sur une fiche.

Cette gestion est interne à Delphi. Nous ne pourront donc la manipuler que dans un bloc d'exception.
Le code suivant est compilé sans warning car aucune des méthode utilisée n'est abstraite, mais provoquera une exception à l'exécution :

var Liste:TStrings; Chaine:String; begin Try Liste:=TStrings.Create; Try // La méthode Add appelle la méthode abstraite GetCount Liste.Add('Une chaîne'); Except On EAbstractError do MessageDlg('Vous devez implémenter la méthode GetCount', mtError, [mbOK], 0); End ; Finally Liste.Free; end; end;
Vous pouvez créer une instance de classe possédant une méthode abstraite dans ce cas le compilateur vous avertira par le message :

175. Construction d'instance de '<...>' contenant des méthodes abstraites.
La seule chose à respecter est de ne pas appeler la ou les méthodes abstraites directement ou indirectement.

Détails sur la déclaration Abstract


4. Les prédéclarations de classes


Le rôle d'une prédéclaration de classe est similaire au mot clés forward utilisé avec les procédures et les fonctions. Cela permet à d'autres classes ou méthodes d'appeler une classe déclarée avant qu'elle ne soit effectivement définie.

Les prédéclarations sont parfois indispensables dans le cas de déclarations croisées entre deux classes.

Déclarations partielles, extraites de l'unité Classes de la VCL :

{ Forward class declarations } TStream = class; TFiler = class; TReader = class; TWriter = class; TComponent = class; …
Ici par exemple ces prédéclarations permettent d'utiliser la classe TFiler dans un entête de méthode, sans avoir à spécifier la totalité de la déclaration de la classe.

Toujours dans l'unité Classes de la VCL :

TPersistent = class(TObject) … protected procedure DefineProperties(Filer: TFiler); virtual; … // Et plus loin dans le code de l'unité la déclaration compléte de la classe TFiler { TFiler } … TFiler = class(TObject) private FStream: TStream; FBuffer: Pointer; …
Une prédéclaration de classe suffit pour utiliser une référence de classe.

... TComponent = class; TComponentClass = class of TComponent;
Le compilateur accepte la syntaxe suivante mais considérera la déclaration compléte de classe plus avant dans le code comme une redéclaration de classe.

... // Prédéclaration erronée TUneClasseDeBase = class(TObject);

5. Les pointeurs de méthode d'objet


Pour pointer une méthode d'une instance d'objet, vous devez ajouter les mots of object au nom de type.

Par exemple les types suivants sont des pointeurs de méthode :

type TMethode = procedure of object; TNotifyEvent = procedure(Sender: TObject) of object;
Un pointeur de méthode est en fait une paire de pointeurs, le premier stocke l'adresse d'une méthode et le second une référence à l'objet auquel appartient la méthode.

Etant donné les déclarations :

type TNotifyEvent = procedure(Sender: TObject) of object; TMainForm = class(TForm) procedure ButtonClick(Sender: TObject); ... end; var MainForm: TMainForm; OnClick: TNotifyEvent
vous pouvez utiliser l'affectation suivante :

OnClick := MainForm.ButtonClick;
Les déclarations suivantes sont possibles :

type Fabrique = Function ():Byte of Object; Fabrique = Function (Reference : TControlClass; MonParent : TWinControl): TControl of Object;
Le code suivant a été proposé par Bloon

Dans la partie published d'une Form, ajoutez une méthode nommée Fermeture qui se contente d'appeler la méthode Close.
Ensuite utilisez l'enregistrement TMethod déclaré dans l'unité system:

type methode = procedure of object; procedure TForm1.Button1Click(Sender: TObject); var m : TMethod; begin m.Data := self; m.Code := self.MethodAddress('Fermeture'); methode(m); end;
Pour passer un paramètre :

Code: type methode = procedure (s : string) of object; procedure TForm1.Button1Click(Sender: TObject); var m : TMethod; begin m.Data := self; m.Code := self.MethodAddress('Fermeture'); methode(m)('Enregistre'); end;
Extrait de l'aide en ligne de Delphi

TMethod est un enregistrement utilisé pour stocker les champs Code et Data comme étant du type Pointer. type TMethod = record Code, Data: Pointer; end;

6. Les références de classe ou métaclasses


Parfois des opérations sont effectuées sur la classe même et pas sur une instance de la classe (c'est-à-dire sur un objet).
Vous pouvez toujours désigner une classe spécifique par son nom, mais dans certains cas il est nécessaire de déclarer des variables ou des paramètres qui prennent des classes pour valeur. Il faut alors utiliser des types référence de classe appelées parfois métaclasses, c'est à dire une classe regroupant d'autres classes dérivées de la même classe ancêtre.

Une référence de classe indique un pointeur sur une zone mémoire contenant la description de la classe.

Un type référence de classe est désigné par une construction de la forme :

class of type
type est un type classe.

La ligne suivante déclare une référence de classe appelée TClass référençant la classe TObject.

type TClass = class of TObject;
Et ici nous déclarons une variable appelée ToutObjet qui peut contenir une référence sur n'importe quelle classe.

var ToutObjet : TClass;
Une référence de classe est un type prédéfini du langage, voici un extrait de la grammaire du Pascal Objet :

... Type -> IdentType -> TypeSimple -> TypeStruc -> TypePointeur -> TypeChaîne -> TypeProcédure -> TypeVariant -> TypeRefClass TypeRestreint -> TypeObjet {* Borland recommande ne plus utiliser ce type pour la programmation objet} -> TypeClasse -> TypeInterface TypeRefClass -> CLASS OF IdentType ...
Les types référence de classe sont utiles quand vous voulez désigner une méthode de classe ou un constructeur virtuel pour une classe ou un objet dont le type réel est inconnu à la compilation.

Les références de classes sont utilisées dans les situation suivantes :

  • Avec un constructeur virtuel pour créer un objet dont le type réel est inconnu au moment de la compilation
  • Avec une méthode de classe pour effectuer une opération sur une classe dont le type réel est inconnu au moment de la compilation
  • Comme opérande droite d'un opérateur is en vue d'accomplir un transtypage controlé lorsque le type est indéterminée au moment de la compilation
  • Comme opérande droite d'un opérateur as en vue d'accomplir un transtypage controlé vers un type indéterminée au moment de la compilation

Note :
La définition d'un type référence de classe ne peut pas se faire directement dans une déclaration de variable ou dans une liste de paramètres.

// Déclaration impossible Var TClass : class of TObject; // Déclaration impossible Function GetSize(Classe : class of TObject ):Integer ;
Rappel
Quand la déclaration d'un type classe ne spécifie pas d'ancêtre, la classe hérite directement de TObject.

Donc la déclaration suivante :

type TMaClasse = class ... end;
est équivalente à :

type TMaClasse = class(TObject) ... end;
Borland recommande cette dernière forme dans un souci de lisibilité.

De plus l'utilisation de la déclaration d'objet suivante, héritée du Turbo Pascal, est proposée dans un souci de compatibilité ascendante, mais son utilisation est déconseillée :

type nomTypeObjet = object(typeObjetAncêtre) listeMembre end;

6.1. Affectation et variable de classe


Une valeur de type référence de classe est compatible en terme d'affectation avec tout type référence de classe ancêtre.

Extrait de l'unité Classes de la VCL :

{ TComponent class reference type } TComponentClass = class of TComponent; … TComponent = class(TPersistent) Private // Déclaration compléte de la classe … TControl = class(TComponent) Private // Déclaration compléte de la classe … TControlClass = class of TControl;
Par exemple pour les variables suivantes ces affectations sont valides :

Var ComponentClass : TComponentClass ; ControlClass : TControlClass Begin ComponentClass := TComponent ; // Valide ComponentClass := TControl; // Valide ...
Alors que dans ces affectations seul la seconde est valide :

Var ComponentClass : TComponentClass ; ControlClass : TControlClass Begin ControlClass := TComponent ; // Invalide ControlClass := TControl; // Valide
La première est invalide car la classe TComponent n'est pas une classe descendante de TControl et n'est donc pas une valeur de type TControlClass.

De même lors d'une vérification de type on doit vérifier des classes descendantes vers les classes ancêtre car une instance de la classe descendante est aussi une instance de la classe ancêtre.

Si on souhaite connaître la classe exacte d'une instance de classe afin d'effectuer un traitement particulier :

if Instance is TControl thenelse if Instance is TComponent thenelse if Instance is TPersistent thenelse if Instance is TObject then
Note
Une variable de type référence de classe peut contenir la valeur Nil, dans ce cas elle ne fait référence à aucune classe.

ControlClass := Nil;
if ControlClass = Nil then Exit;
En fait, c'est par l'utilisation des références de classe et des constructeurs virtuels que le concepteur de forme de Delphi peut créer les composants qui sont placés sur des fiches.


6.2. Constructeur virtuel


Pour des explications plus détaillées je vous renvoie vers les cours suivant au lieu de réécrire ce qui a été déjà très bien dit, nous appliquerons donc le principe de la réutilisation :-)

Cours sur les constructeurs virtuel
Heritage et notion évoluées

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

Par exemple dans le code qui suit la variable Reference est une référence de classe pour la classe TClasseDeBase. Par conséquent, toutes les classes dérivées de TClasseDeBase peuvent être assignées à Reference.

TClasseDeBase = class( TObject ) Numero: Byte; constructor Create; { Constructeur statique } end; TClasseDescendante = class( TClasseDeBase ) constructor Create; { Constructeur statique } end; TExemple = class( TClasseDeBase ) constructor Create; { Constructeur statique} end; TClassDeClasseDeBase = class of TClasseDeBase; { Référence de classe } var Form1: TForm1; Reference : TClassDeClasseDeBase; { Variable référence de classe } Instance : TClasseDeBase; { Variable d'instance de classe }
Le code de cet exemple
Afin de visualiser le comportement entre un constructeur statique et un constructeur virtuel ce code est volontairement erroné, bien que sa syntaxe soit correcte.
Exécutez ce programme et créez un objet de chaque classe.

Dans cet exemple tout fonctionne correctement en dehors du fait que le contenu de la variable Numero ne correspond pas à ce que nous attendons. C'est à dire que les données spécifiques aux classes descendantes ne sont pas initialisées.

Instance := Reference.Create; Memo1.Lines.Add('Création d''une instance de la classe '+Instance.Classname);
Le code précédant crée bien une instance de la classe voulue mais le constructeur appelé est toujours celui de la classe ancêtre. A noter que si nous déclarons une méthode virtuelle dans chacune de ces classes, la méthode de la classe réelle est bien appelée.

Modifions les déclarations de classe comme ceci :

TClasseDeBase = class( TObject ) Numero: Byte; constructor Create; Virtual; { Constructeur Virtuel } end; TClasseDescendante = class( TClasseDeBase ) constructor Create; override; { Constructeur surchargé } end; TExemple = class( TClasseDeBase ) constructor Create; override; { Constructeur surchargé } end;
Le code modifié
L'ajout de la directive Virtual ou override pour les classes descendantes permet bien d'appeler le constructeur de la classe référencée et non pas celui de la classe ancêtre.


6.3. Le polymorphisme


Polymorphe
"Qui peut se présenter sous des formes différentes."

Polymorphisme ou polymorphie
"Caractére de ce qui est polymorphe."

Nous avons pu voir que la référence de classe associé au constructeur virtuel permet l'implémentation du polymorphisme, c'est à dire permettre à une instance d'avoir plusieurs formes ou plutôt plusieurs comportements.
La référence d'objet utilisée se présentera à un moment t sous une seule forme mais se comportera de maniére différente tout au long de l'exécution du programme.

A noter qu'il ne faut pas confondre le polymorphisme avec l'héritage multiple, voir aussi le pattern Wrapper. C'est à dire permettre à un objet de se présenter sous une seule forme tout au long de son existence mais pouvant se comporter de différente maniére.

Détails sur le polymorphisme

http://rmdiscala.developpez.com/cours/livres/basesinfo5.3.pdf

http://fbeaulieu.developpez.com/guide/14-objets-2.html#tdm14-4-2


6.4. Etude de cas


Nous souhaitons lors de l'exécution du programme créer une instance de TControl parmis les 3 classes descendantes suivantes : TEdit,TButton,TMemo.
La classe que nous souhaitons instancier est donc inconnue au moment de la compilation. Nous utiliserons la référence de classe TControlClass déclarée dans l'unité classes qui nous permettra de manipuler les 3 classes citées.
Nous utiliserons une seule variable référence de classe pouvant être tour à tour soit un TEdit, soit un TButton, soit un TMemo.


6.4.1. Gestion des 3 classes souhaitées


Cet exemple implémente de maniére simple le polymorphisme, ici ce qui vas différencier chaque control c'est la maniére dont il s'affiche à l'écran. Le control se comportera différemment selon sa classe réelle à l'exécution. Il existe bien sur d'autre différence mais l'affichage étant la plus 'remarquable'.

L'interface de notre programme permettra donc de créer une instance parmis ces 3 classes.
Le bouton Créer utilisera une méthode regroupant la sélection de la classe puis la création de l'instance.
Le bouton Fabriquer utilisera une méthode dédiée à la création d'instance de classe TControlClass.

Première version du code

Exécutez le programme et créez une instance de chaque classe.

1 procedure TForm1.Creer; 2 Begin 3 FreeAndNil(Instance); 4 case rgClasseObjet.ItemIndex of 5 0 : ControlClass:=TEdit; 6 1 : ControlClass:=TButton; 7 2 : ControlClass:=TMemo; 8 end; 9 // Appel la méthode de classe 10 MemoLog.Lines.Add('Création d''une instance de la classe '+ControlClass.ClassName); 11 // provoque une erreur lors de la compilation : 12 // MemoLog.Lines.Add('Création d''une instance de classe '+ControlClass.ClassType); 13 14 Instance :=ControlClass.Create(Self ); 15 Instance.Parent:=Panel1; 16 end;
La ligne 3 permet de supprimer l'instance précédement créer.
Les lignes 5,6,7 affectent à la variable référence de classe ControlClass le type de classe que l'on souhaite instancier.
La ligne 10 appel la méthode de classe (que nous aborderons un peu plus tard) Classname afin d'afficher le type de la classe sélectionnée.
La ligne 12 nous démontre qu'une variable référence de classe ne peut pas effectuer d'appel sur des méthodes autres que des méthode de classes.
Le compilateur renvoie :

77. Forme d'appel de méthode autorisée seulement pour méthodes de classe
La ligne 14 créé une instance de classe via l'appel du constructeur virtuel de la classe référencée dans la variable ControlClass. Les classes TEdit,TButton,TMemo possédent toute un constructeur Virtuel.
Et enfin la ligne 15 renseigne le parent du control créé permettant de l'afficher dans le panel placé à droite dans la fenêtre principale.

La fonction suivante est appelé via le code associé au bouton Fabriquer :

function TForm1.FabriqueUnControl(Reference : TControlClass; MonParent : TWinControl): TControl; begin MemoLog.Lines.Add('Création d''une instance de la classe '+Reference.ClassName); Result:=Reference.Create(Self); // Propriétaire : Il contrôle quand le composant est libéré Result.Parent:=MonParent; // Parent : C'est le contrôle fenêtré contenant le contrôle créé end;
On retrouve donc tous les acteurs de la procédure Créer mais ici on généralise l'utilisation du traitement de création d'instance. L'ajout du propriétaire du control (self) dans la liste des paramètres permettrait d'isoler cette fonction dans une unité.

Un autre exemple d'utilisation de type référence de classe est fourni par la déclaration du constructeur de TCollection, extrait de l'unité Classes de la VCL :

type TCollectionItemClass = class of TCollectionItem; ... constructor Create(ItemClass: TCollectionItemClass);
Cette déclaration indique que pour créer un objet instance de TCollection, il faut transmettre au constructeur le nom d'une classe descendant de TCollectionItem.


6.4.2. Ajout de la classe TObject


Pour aller plus loin on souhaite créer une instance de la classe TObject. Ajoutons donc dans les méthodes Creer et Fabriquer le choix de cette classe.

Seconde version du code

... procedure TForm1.Creer; Begin FreeAndNil(Instance); case rgClasseObjet.ItemIndex of 0 : ControlClass:=TEdit; 1 : ControlClass:=TButton; 2 : ControlClass:=TMemo; 3 : ControlClass:=TObject; end; ... procedure TForm1.Fabriquer; Begin FreeAndNil(Instance); case rgClasseObjet.ItemIndex of 0 : Instance:=FabriqueUnControl(TEdit,Panel1); 1 : Instance:=FabriqueUnControl(TButton,Panel1); 2 : Instance:=FabriqueUnControl(TMemo,Panel1); 3 : Instance:=FabriqueUnControl(TObject,Panel1); end; end; ...
Si nous essayons de compiler ce projet la compilation échoue sur le message d'erreur suivant:

Types incompatibles : '<TControlClass>' et '<Class Reference>'
La classe TObject n'étant pas un descendant de la classe TControl elle ne peut donc être assignée à la variable référence de classe ControlClass. Mais comme nous manipulons des classes nous pouvons donc utiliser le transtypage.

Modifiez le code dans la méthode Créer comme ceci:

3 : ControlClass:=TControlClass(TObject);
et dans la méthode Fabriquer :

3 : Instance:=FabriqueUnControl(TControlClass(TObject),Panel1);
Troisième version du code

Exécutez le programme et créez une instance de chaque classe. Malheureusement la création d'une instance de classe TObject n'est pas possible car à la différence de la méthode create des classes dérivées de TControl, celle de la classe TObject ne nécessite aucun paramètre.

Dans un premier nous pouvons ajouter un bloc d'exception permettant de gérer ce probléme :

procedure TForm1.Creer; Begin FreeAndNil(Instance); case rgClasseObjet.ItemIndex of 0 : ControlClass:=TEdit; 1 : ControlClass:=TButton; 2 : ControlClass:=TMemo; 3 : ControlClass:=TControlClass(TObject); end; // Appel la méthode de classe MemoLog.Lines.Add('Création d''une instance de la classe '+ControlClass.ClassName); Try Instance :=ControlClass.Create(Self ); Instance.Parent:=Panel1; Except On E:EAccessViolation do MessageDlg('La méthode create n''est pas adaptée pour la classe '+ControlClass.ClassName, mtError, [mbOK], 0); end; end;
Si vous exécutez le programme ainsi modifié, le code intercepte l'erreur mais il n'est toujours pas possible de créer notre instance.
Ajoutons l'instruction de création dans le bloc d'exception :

Try Instance :=ControlClass.Create(Self ); Instance.Parent:=Panel1; Except On E:EAccessViolation do Instance :=ControlClass.Create; end;
Personnellement ce détournement ne me plait pas trop. Une solution plus élégante serais de vérifier le type de la classe avec l'opérateur is. Malheureusement l'utilisation de l'opérateur is permettant une vérification de type dynamique n'est pas possible sur une référence de classe.

If ControlClass is TEdit then ...
Par contre une référence de classe contient comme valeur un type de classe, on peut donc la tester de la maniére suivante :

If ControlClass=TObject then ...
Le code suivant est aussi possible :

var ControlClass : TControlClass; ObjetClass : TClass; begin ControlClass:=TEdit; ObjetClass:= TPersistent; If ControlClass=ObjetClass then ...
mais pas celui-ci

If ControlClass=TClass then ...
Par contre si nous testons la référence de classe ControlClass aprés lui avoir affecté la valeur TEdit ce test ( If ControlClass=TObject ) fonctionne toujours. Le résultat est correct mais dans ce cas il nous faut tester les 4 classes, des classes descendantes vers la classe ancêtre. Dans notre cas présent la possibilité de tester une branche compléte d'une hiérarchie d'héritage faciliterais la lecture et l'écriture du code. La méthode de classe InheritsFrom permet ce type de vérification.

Texte issue de l'aide en ligne de Delphi:

TObject.InheritsFrom Détermine la relation entre deux types d'objet. class function InheritsFrom(AClass: TClass): Boolean; Description Utilisez la méthode InheritsFrom pour déterminer si un type de classe donné est l'ancêtre d'un objet. InheritsFrom renvoie True si le type d'objet spécifié par le paramètre AClass est bien l'ancêtre du type de l'objet, ou le type même de l'objet. Sinon, elle renvoie False. Remarque : Les opérateurs is et as utilisent dans leur implémentation la méthode InheritsFrom. Cependant, l'opérateur is ne peut déterminer la relation d'héritage que pour une instance. Alors que InheritsFrom, comme méthode de classe, est capable de déterminer la relation entre des références de classe.
En utilisant cette méthode on peut donc vérifier si la classe référencée hérite bien de TControl, modifions le code source de la méthode Créer et fabriquer en supprimant le bloc d'exception devenu inutile:

procedure TForm1.Creer; Begin FreeAndNil(Instance); case rgClasseObjet.ItemIndex of 0 : ControlClass:=TEdit; 1 : ControlClass:=TButton; 2 : ControlClass:=TMemo; 3 : ControlClass:=TControlClass(TObject); end; // Appel la méthode de classe MemoLog.Lines.Add('Création d''une instance de la classe '+ControlClass.ClassName); If ControlClass.InheritsFrom(TControl) then begin Instance :=ControlClass.Create(Self ); Instance.Parent:=Panel1; end else Instance :=ControlClass.Create; end;
Quatrième version du code

Si vous compilez ce programme ainsi modifié la compilation échoue. Nous devons effectuer les transtypages suivant pour pouvoir générer l'exécutable :

... else TObject(Instance) :=TObject(ControlClass).Create; end;
Exécutez ce programme, puis créez tour à tour une instance de chaque classe. Nous rencontrons encore le problème lors de la création d'une instance de la classe TObject.

Le transtypage de la référence d'objet Instance, en utilisant l'identificateur de type classe TObject est correcte.
Le transtypage de la référence de classe ControlClass ne l'est pas, on doit utiliser l'identificateur de type référence de classe TClass.

... else TObject(Instance) :=TClass(ControlClass).Create; end;
Dans le premier cas le transtypage induit en erreur le compilateur qui traite ControlClass comme une instance et non comme une référence de classe.

// Identificateur de type référence de classe type TClass = class of TObject; // Identificateur de type classe type TPersistent = class(TObject);
Ce type de transtypage est nécessaire lorsque l'on souhaite récupérer le type de classe d'une instance, par exemple la méthode Classtype renvoie une valeur de référence TClass :

ControlClass:=TControlClass(Sender.ClassType);

6.4.3. Ajout d'une classe ascendante


Dernière version du code

Pour terminer nous allons ajouter la création d'une instance de la classe TComponent en ajoutant la vérification de type comme nous l'avions vu précédement.

procedure TForm1.Creer; Begin FreeAndNil(Instance); case rgClasseObjet.ItemIndex of 0 : ControlClass:=TEdit; 1 : ControlClass:=TButton; 2 : ControlClass:=TMemo; 3 : ControlClass:=TControlClass(TObject); 4 : ControlClass:=TControlClass(TComponent); end; // Appel la méthode de classe MemoLog.Lines.Add('Création d''une instance de la classe '+ControlClass.ClassName); // Vérification d'appartenance à une Hiérarchie de classe If ControlClass.InheritsFrom(TControl) then begin Instance:=ControlClass.Create(Self ); // Vérification d'appartenance à une classse If ControlClass=TComponent // TComponent ne posséde pas de propriétés Parent then Exit; Instance.Parent:=Panel1; end else TObject(Instance):=TClass(ControlClass).Create; end;
Ces exemples nous ont permis d'aborder la manipulation des méthodes de classe.

Vous trouverez dans le projet de démonstration Resxplor.dpr un exemple complet de gestion des références de classes
..\Delphi\Demos\Resxplor\exeimage.pas

Vous noterez l'appel suivant dans la fonction TResourceList.List qui permet la création d'une instance sans déclaration de variable intermédiaire :

ResItem := GetResourceClass(FResType).CreateItem(Self, DirEntry);

7. Les méthodes de classe


Nous les avons utilisées précédemment sans trop connaître leur principe. Les méthodes de classe permettent d'obtenir des informations sur une référence de classe et pas sur une instance de classe. Elle peuvent être appelées sans création d'instance de classe et leur accés peut se faire par des références d'objet ou par des références de classe comme nous avons pu le voir.

La déclaration d'une méthode de classe doit commencer par le mot clé class. Exemple de déclaration de la méthode ClassName de la classe TObject :

class function ClassName: ShortString;
La déclaration de définition d'une méthode de classe doit également commencer par le mot clé class :

class function TObject.ClassName: ShortString;
Dans le cas d'une méthode de classe l'utilisation de self à une signification différente, il s'agit d'une variable de type référence de classe contenant la classe effective de l'objet.

Self : Class of ClassType
Dans ce cas Self n'est pas une référence d'objet, vous ne devez donc pas accéder aux champs, propriétés et méthodes normales ( ormis bien sûr d'autre méthode de classe et les constructeurs).

Exemple d'utilisation :

Var UnComposant:TButton; UneReference: TControlClass; begin // Bien que UnComposant ne soit pas initialisé, on peut utiliser ces méthodes de classe ShowMessage('Référence d''objet '+UnComposant.ClassName); UneReference:=TControlClass(UnComposant.ClassType); ShowMessage('Référence de classe '+UneReference.ClassName); UnComposant:=TButton.Create(Self); end;
De telles méthodes peuvent permettre un comportement spécifiques à la classe entière, par exemple un compteur de références. Les méthodes de classe peuvent être virtuelle et surchargée comme n'importe quelle autre méthode.

Code source de démonstration


7.1. Le pattern Singleton


Un Singleton prend en charge la création d'une instance de classe et s'assure qu'il n'existera qu'une et seule instance tout au long de l'exécution d'un programme.

L'implémentation de ce pattern est un bon exemple d'utilisation des méthodes de classe.
Vous trouverez sur le site de Borland US un exemple d'implémentation du pattern Singleton .


7.2. Retrouver une classe par son nom


Delphi ne propose pas en dehors de la méthode GetClass la possibilité d'obtenir une classe à partir d'un nom de classe contenue dans une chaîne de caractéres pour l'assigner à une référence de classe :

TControlClass=TEdit.Text; // Invalide
L'utilisation de la méthode GetClass permet de récupérer une classe à partir d'un nom de classe :

TControlClass=GetClass(TEdit.Text);
Par contre la classe doit être recensée par RegisterClass pour que la méthode GetClass puisse la trouver. Il faut donc que les classes recensées dérivent de la classe TPersistent.

Et dans le cas suivant :

UneClasse(GetClass(TEdit.Text)).Traitement(params)
il faut que :

  • Traitement soit une méthode de classe. Sinon il n'est pas possible d'effectuer l'appel de méthode de cet exemple.
  • Traitement soit une méthode virtuelle ou dynamique. Sinon on risque de perdre l'héritage et donc de ne pas appeler la bonne méthode en fonction de la classe réelle.

La solution suivante proposée par Bloon implémente un mécanisme d'enregistrement pour toutes les classes.
Dans un premier temps nous pourrions utiliser la classe TClassList mais la classe TStringList à les avantages suivant :

  • si vous utilisez un nombre important de classes la propriété Sorted permet d'accélérer la recherche
  • la persistence, c'est à dire la possibilité d'enregistrer cette liste sur disque.

Pour enregistrer le nom d'une classe et son type :

Var mesClasses :TStringList; begin ... // Enregistre le nom de la classe et le type de la classe mais pas l'objet mesClasses.AddObject('TMaClasse',TObject(TMaClasse)); ...
Pour retrouver une classe par son nom :

Var mesClasses : TStringList; MaClasse : TControlClass; i : Integer; begin ... // Recherche dans la liste d'association i := mesClasses.IndexOf('TEdit'); if (i = -1) // si pas trouvé, on renvoie nil then MaClasse := nil; // si trouvé, on renvoie la classe associée else MaClasse:= TClass(mesClasses.objects[i]); ...
En utilisant les méthode de classe il est également possible de faire :

procedure addClass(UneClasse : TClass); begin mesClasses.AddObject(UneClasse.ClassName,TObject(UneClasse)); end;
Exemple :

type TRefMesClasses = class of TMesClasses; var classByName : TStringList; // Enregistrer une classe procedure registerClassByName(classe: TRefMesClasses); begin // Ajout de la classe si elle n'est pas déjà référencée if (classByName.IndexOf(classe.ClassName) = -1) then begin classByName.AddObject(classe.ClassName,TObject(classe)); end; end; // Obtenir une classe par son nom function getClassByName(nom: string): TRefMesClasses; var i : integer; begin // Recherche dans la liste des classByName i := classByName.IndexOf(nom); // si pas trouvé, on renvoie nil if (i = -1) then begin result := nil; end // si trouvé, on renvoie la classe associée else begin result := TRefMesClasses(classByName.objects[i]); end end; // Au début de l'application, enregistrer les classes : classByName := TStringList.Create; registerClassByName(MaClasse1); ... // Fin d'application, libérer classByName classByName.Free; // Utilisation : getClassByName(Edit1.Text).Traitement...
Cette approche générique nous permet d'améliorer le premier code source présenté dans le chapitre "les références de classe ou métaclasses":

Donc à la place de :

... case rgClasseObjet.ItemIndex of 0 : ControlClass:=TEdit; 1 : ControlClass:=TButton; 2 : ControlClass:=TMemo; 3 : ControlClass:=TControlClass(TObject); end; MemoLog.Lines.Add('Création d''une instance de la classe '+ControlClass.ClassName); Instance :=ControlClass.Create(Self ); Instance.Parent:=Panel1;
on écrira :

procedure TForm1.Creer; Begin if (rgClasseObjet.ItemIndex <= -1) then Exit; FreeAndNil(Instance); with rgClasseObjet do // Appel la méthode de classe Instance := TControlClass(Items.Objects[ItemIndex]).Create(Self); Instance.Parent:=Panel1; MemoLog.Lines.Add('Création d''une instance de la classe '+Instance.ClassName); end;
La méthode FormCreate devenant :

procedure TForm1.FormCreate(Sender: TObject); begin ControlClass :=Nil; Instance :=Nil; rgClasseObjet.Items.AddObject('TEdit',TObject(TEdit)); rgClasseObjet.Items.AddObject('TButton',TObject(TButton)); rgClasseObjet.Items.AddObject('TMemo',TObject(TMemo)); end;



Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La licence d'utilisation Open Content ( http://opencontent.org/opl.shtml) vous permet d'utiliser librement les documents publiés sur mon espace personnel à condition de mentionner clairement le nom de son auteur avec un lien sur mon espace personnel de ce site et de garder son caractère "Open Content".

Responsables bénévoles de la rubrique Delphi : NoisetteProd et Pedro - Contacter par EMail :
Vos questions techniques : forum d'entraide Delphi - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.