Classes et objets (Delphi)

De RAD Studio
Aller à : navigation, rechercher

Remonter à Classes et objets - Index

Cette rubrique couvre les sujets suivants :

  • Syntaxe de déclaration des classes
  • Héritage et portée
  • Visibilité des membres de classe
  • Déclarations avancées et classes mutuellement dépendantes

Types classe

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

  • Un champ est essentiellement une variable faisant partie d'un objet. Comme les champs d'un enregistrement, les champs d'une classe représentent des éléments de données qui existent dans chaque instance de la classe.
  • Une méthode est une procédure ou une fonction associée à une classe. La plupart des méthodes portent sur des objets, c'est-à-dire sur des instances d'une classe. Certaines méthodes, appelées méthodes de classe, portent sur les types classe mêmes.
  • Une propriété est une interface avec les données associées à un objet (souvent stockées dans un champ). Les propriétés ont des spécificateurs d'accès qui déterminent comment leurs données sont lues et modifiées. Pour le reste d'un programme (hors de l'objet même), une propriété apparaît à bien des points de vue comme un champ.

Les objets sont des blocs de mémoire alloués dynamiquement dont la structure est déterminée par leur type 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 objets sont créés et détruits par des méthodes spéciales appelées constructeurs et destructeurs.

Une variable de type classe est en fait un pointeur qui référence 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. Cependant, il n'est pas nécessaire de déréférencer explicitement une variable de type classe pour accéder à l'objet qu'elle désigne. Par exemple, SomeObject.Size := 100 assigne la valeur 100 à la propriété Size de l'objet référencé par SomeObject ; vous ne devez pas l'écrire sous la forme SomeObject^.Size := 100.

Un type classe doit être déclaré et nommé avant de pouvoir être instancié. (Il n'est donc pas possible de définir un type classe dans une déclaration de variable.) Déclarez les classes seulement dans la portée la plus large d'un programme ou d'une unité, mais pas dans une déclaration de procédure ou de fonction.

La déclaration d'un type classe a la forme suivante :

 type
    className = class [abstract | sealed] (ancestorClass)
        type
          nestedTypeDeclaration
        const
	  nestedConstDeclaration
        memberList
    end;

Eléments requis de la déclaration de type classe

  • className peut être tout identificateur valide.
  • memberList déclare les membres de la classe : des champs, des méthodes et des propriétés.

Eléments facultatifs de la déclaration de type classe

  • abstract. Il est possible de déclarer toute une classe comme étant abstraite (abstract) même si elle ne contient aucune méthode virtuelle abstraite.
  • sealed. Une classe sealed ne peut pas être étendue par le biais de l'héritage.
  • ancestorClass. Si vous omettez (ancestorClass), la nouvelle classe hérite directement de la classe System.TObject prédéfinie. Si vous incluez (ancestorClass) en laissant vide memberList, vous pouvez omettre end.
  • nestedTypeDeclaration. Les types imbriqués permettent de regrouper des types conceptuellement associés et d'éviter les conflits de noms.
  • nestedConstDeclaration. Les constantes imbriquées permettent de regrouper des types conceptuellement associés et d'éviter les conflits de noms.

Une classe ne peut pas être à la fois abstract et sealed. La syntaxe [abstract | sealed] (les crochets [ ] et le symbole | entre les crochets) est utilisée pour spécifier qu'un seul des mots clés facultatifs sealed ou abstract peut être utilisé. Seul le mot-clé sealed ou abstract est significatif. Les crochets et le symbole doivent être supprimés.

Remarque: Delphi permet l'instanciation d'une classe déclarée abstract, pour la compatibilité descendante, mais cette fonctionnalité ne doit plus être utilisée.

Les méthodes apparaissent dans une déclaration de classe sous la forme d'en-tête de fonction ou de procédure sans le corps. La déclaration de définition de chaque méthode est faite ailleurs dans le programme.

Voici par exemple, la déclaration de la classe TMemoryStream de l'unité Classes :

TMemoryStream = class(TCustomMemoryStream)
  private
    FCapacity: Longint;
    procedure SetCapacity(NewCapacity: Longint);
  protected
    function Realloc(var NewCapacity: Longint): Pointer; virtual;
    property Capacity: Longint read FCapacity write SetCapacity;
  public
    destructor Destroy; override;
    procedure Clear;
    procedure LoadFromStream(Stream: TStream);
    procedure LoadFromFile(const FileName: string);
    procedure SetSize(const NewSize: Int64); override;
    procedure SetSize(NewSize: Longint); override;
    function Write(const Buffer; Count: Longint): Longint; override;
    function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override;
  end; // deprecated 'Use TBytesStream';

Classes.TMemoryStream descend de Classes.TCustomMemoryStream, en héritant de la plupart de ses membres. Mais elle définit, ou redéfinit, plusieurs propriétés et méthodes, y compris sa méthode destructeur, Destroy. Son constructeur, Create, est hérité sans modification de System.TObject, et n'est donc pas redéclaré. Chaque membre est déclaré comme private, protected ou public (cette classe n'a pas de membre published). Ces termes sont expliqués ci-dessous.

Etant donné cette déclaration, vous pouvez créer une instance de TMemoryStream comme suit :

var
  stream: TMemoryStream;

begin
  stream := TMemoryStream.Create;

Héritage et portée

Quand vous déclarez une classe, vous pouvez spécifier son ancêtre immédiat. Par exemple :

  type TSomeControl = class(TControl);

déclare une classe appelée TSomeControl qui descend de Vcl.Controls.TControl. Un type classe hérite automatiquement de tous les membres de son ancêtre immédiat. Chaque classe peut déclarer de nouveaux membres et redéfinir les membres hérités. Par contre, une classe ne peut pas retirer des membres définis dans un ancêtre. Ainsi TSomeControl contient tous les membres définis dans Vcl.Controls.TControl et dans chacun des ancêtres de Vcl.Controls.TControl.

La portée de l'identificateur d'un membre commence à l'endroit où le membre est déclaré et se poursuit jusqu'à la fin de la déclaration de la classe et s'étend à tous les descendants de la classe et les blocs de toutes les méthodes définies dans la classe et ses descendants.

TObject et TClass

La classe System.TObject, déclarée dans l'unité System, est l'ancêtre ultime de toutes les autres classes. System.TObject ne définit que quelques méthodes, ainsi qu'un constructeur et un destructeur de base. Outre la classe System.TObject, l'unité System déclare le type référence de classe System.TClass :

  TClass = class of TObject;

Quand la déclaration d'un type classe ne spécifie pas d'ancêtre, la classe hérite directement de System.TObject. Ainsi :

type TMyClass = class
      ...
     end;

est l'équivalent de :

type TMyClass = class(TObject)
      ...
     end;

Cette dernière forme est recommandée dans un souci de lisibilité.

Compatibilité des types classe

Un type classe est compatible pour l'assignation avec ses ancêtres. Une variable d'un type classe peut donc référencer une instance de tout type descendant. Par exemple, soit ces déclarations :

type
  TFigure = class(TObject);
  TRectangle = class(TFigure);
  TSquare = class(TRectangle);
var
 Fig: TFigure;

il est possible d'assigner à la variable Fig des valeurs de type TFigure, TRectangle et TSquare.

Types objet

Le compilateur Delphi propose une autre syntaxe aux types classe. Vous pouvez déclarer des types objet en utilisant la syntaxe :

type objectTypeName = object (ancestorObjectType)
        memberList
     end;

objectTypeName est un identificateur valide, (ancestorObjectType) est facultatif et memberList déclare les champs, méthodes et propriétés. Si (ancestorObjectType) n'est pas spécifié, le nouveau type n'a pas d'ancêtre. Les types objet ne peuvent pas avoir de membres publiés.

Comme les types objet ne descendent pas de System.TObject, ils ne disposent pas de constructeurs, de destructeurs ou d'autres méthodes prédéfinies. Vous pouvez créer des instances d'un type objet en utilisant la procédure New et les détruire en utilisant la procédure Dispose. Vous pouvez également déclarer tout simplement des variables d'un type objet comme pour un enregistrement.

Les types objet sont seulement pris en charge dans un souci de compatibilité descendante. Leur utilisation n'est pas recommandée.

Visibilité des membres de classe

Chaque membre d'une classe a un attribut appelé visibilité, indiqué par l'un des mots réservés suivants : private, protected, public, published ou automated. Par exemple :

  published property Color: TColor read GetColor write SetColor;

déclare une propriété publiée appelée Color. La visibilité détermine où et comment il est possible d'accéder à un membre : private (privée) représente l'accès minimal, protected (protégée) représente un niveau intermédiaire d'accès, public (publique), published (publiée) et automated (automatisée) représentant l'accès le plus large.

Si la déclaration d'un membre apparaît sans son propre spécificateur de visibilité, le membre a la même visibilité que celui qui le précède. Les membres au début de la déclaration d'une classe dont la visibilité n'est pas spécifiée sont par défaut published si la classe a été compilée dans l'état {$M+} ou si elle dérive d'une classe compilée à l'état {$M+} ; sinon ces membres sont public.

Dans un souci de lisibilité, il est préférable d'organiser la déclaration d'une classe en fonction de la visibilité, en plaçant tous les membres private ensemble, suivis de tous les membres protected, et ainsi de suite. De cette manière, chaque mot réservé spécifiant la visibilité apparaît au maximum une fois et marque le début d'une nouvelle "section" de la déclaration. Une déclaration de classe typique doit donc avoir la forme :

type
  TMyClass = class(TControl)
    private
      { private declarations here }
    protected
      { protected declarations here }
    public
      { public declarations here }
    published
      { published declarations here }
  end;

Vous pouvez augmenter la visibilité d'une propriété dans une classe descendante en la redéclarant, mais il n'est pas possible de réduire sa visibilité. Par exemple, une propriété protected peut être rendue public dans un descendant mais pas private. De plus, les propriétés published ne peuvent pas devenir public dans une classe descendante. Pour de plus amples informations, voir Redéclarations et redéfinitions de propriétés.

Membres privés, protégés et publics

Un membre private est invisible hors de l'unité ou du programme dans lequel la classe est déclarée. En d'autres termes, une méthode private ne peut être appelée depuis un autre module, et une propriété ou un champ private ne peut être lu ou écrit depuis un autre module. En plaçant les déclarations de classes associées dans le même module, vous pouvez donner à chaque classe un accès aux membres private d'une autre classe sans rendre ces membres plus largement accessibles. Pour qu'un membre soit visible seulement à l'intérieur de sa classe, il doit être déclaré strict private.

Un membre protected est visible partout dans le module où sa classe est déclarée et dans toute classe descendante, indépendamment du module où la classe descendante apparaît. Une méthode protected peut être appelée, et un champ ou une propriété protected peut être lu ou écrit, dans la définition de toute méthode appartenant à une classe qui descend de celle où le membre protected est déclaré. Généralement, les membres qui ne servent qu'à l'implémentation des classes dérivées sont protected.

Un membre public est visible partout où sa classe peut être référencée.

Spécificateurs de visibilité stricte

Outre les spécificateurs de visibilité private et protected, le compilateur Delphi prend en charge des paramètres de visibilité supplémentaires avec des contraintes d'accès plus importantes. Il s'agit des visibilités strict private etstrict protected.

Les membres de classe dont la visibilité est strict private ne sont accessibles que dans la classe dans laquelle ils sont déclarés. Ils ne sont pas visibles pour les procédures ou fonctions déclarées dans la même unité. Les membres de classe dont la visibilité est strict protected sont visibles dans la classe dans laquelle ils sont déclarés et dans toutes les classes descendantes, quel que soit l'endroit où elles sont déclarées. De plus, quand des membres d'instance (ceux déclarés sans les mots clés class ou class var) sont déclarés strict private ou strict protected, ils ne sont pas accessibles en dehors de l'instance d'une classe dans laquelle ils apparaissent. Une instance d'une classe ne peut pas accéder aux membres d'instance strict private ou strict protected d'autres instances de la même classe.

Remarque: Le mot strict est traité comme une directive dans le contexte d'une déclaration de classe. Dans une déclaration de classe, vous ne pouvez pas déclarer un membre nommé 'strict', mais vous pouvez l'utiliser à l'extérieur d'une déclaration de classe.

Membres publiés

Les membres publiés (published) ont la même visibilité que les membres publics (public). La différence est que des informations de type à l'exécution (RTTI) sont générées pour les membres publiés. Ces informations permettent à une application d'interroger dynamiquement les champs et les propriétés d'un objet et de localiser ses méthodes. Les informations RTTI sont utilisées pour accéder aux valeurs des propriétés lors de la lecture ou de l'enregistrement des fichiers fiche, pour afficher les propriétés dans l'inspecteur d'objets et pour associer des méthodes spécifiques (appelées gestionnaires d'événement) à des propriétés spécifiques (appelées événements).

Les propriétés publiées sont limitées à certains types de données. Les types ordinal, chaîne, classe, interface, variant et pointeur de méthode peuvent être publiés. Tout comme les types ensemble, dans la mesure où les bornes inférieure et supérieure du type de base ont des valeurs ordinales comprises entre 0 et 31. En d'autres termes, l'ensemble doit tenir dans un octet, un mot ou un double mot. Tous les types réels, à l'exception de Real48, peuvent être publiés. Les propriétés d'un type tableau (différentes des propriétés tableau, traitées ci-dessous) ne peuvent pas être publiées.

Certaines propriétés, bien que publiables, ne sont pas totalement prises en charge par le système de flux. Il s'agit des propriétés de types enregistrement, des propriétés tableau de tous les types publiables, et des propriétés des types énumérés qui incluent des valeurs anonymes. Si vous publiez une telle propriété, l'inspecteur d'objets ne l'affichera pas correctement, et la valeur de la propriété ne sera pas conservée quand les objets seront enregistrés sur disque.

Toutes les méthodes sont publiables, mais une classe ne peut publier plusieurs méthodes surchargées portant le même nom. Les champs ne peuvent être publiés que s'ils sont de type classe ou interface.

Une classe ne peut avoir de membres publiés que si elle est compilée avec l'état {$M+} ou si elle descend d'une classe compilée avec l'état {$M+}. La plupart des classes contenant des membres publiés dérivent de Classes.TPersistent, qui est compilé avec l'état {$M+}, il est donc rarement nécessaire d'utiliser la directive $M.

Remarque: Les identificateurs contenant des caractères Unicode ne sont pas autorisés dans les sections publiées de classes ou dans des types employés par des membres publiés.

Membres automatisés (Win32 seulement)

Les membres automatisés ont la même visibilité que les membres publics. La différence est que des informations de type Automation (requises pour les serveurs Automation) sont générées pour les membres automatisés. Les membres automatisés n'apparaissent généralement que dans les classes Win32. Le mot réservé automated est maintenu dans un souci de compatibilité descendante. La classe TAutoObject de l'unité ComObj n'utilise pas automated.

Les restrictions suivantes s'appliquent aux méthodes et propriétés déclarées comme automatisées :

  • Le type de toutes les propriétés, paramètres de propriété tableau, paramètres de méthode et résultats de fonction doivent être automatisables. Les types automatisables sont Byte, Currency, Real, Double, Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool, et tous les types interface.
  • Les déclarations de méthode doivent utiliser la convention d'appel par défaut register. Les méthodes peuvent être virtuelles mais pas dynamiques.
  • Les déclarations de propriété peuvent comporter des spécificateurs d'accès (read et write) mais les autres spécificateurs (index, stored, default et nodefault) ne sont pas autorisés. Les spécificateurs d'accès doivent indiquer un identificateur de méthode utilisant la convention d'appel par défaut register ; les identificateurs de champ ne sont pas autorisés.
  • Les déclarations de propriété doivent spécifier un type. Il n'est pas permis de redéfinir les propriétés.

La déclaration d'une méthode ou d'une propriété automatisée peut inclure une directive dispid. La spécification d'un ID (identificateur) déjà utilisé dans une directive dispid cause une erreur.

Sur la plate-forme Win32, cette directive doit être suivie d'une constante entière qui spécifie un ID de répartition Automation pour le membre. Sinon, le compilateur assigne automatiquement au membre un ID de répartition plus grand que le plus grand ID de répartition utilisé par les méthodes et les propriétés de la classe et de ses ancêtres. Pour de plus amples informations sur l'Automation (sous Win32 uniquement), voir Objets automation.

Déclarations avancées et classes mutuellement dépendantes

Si la déclaration d'un type classe se termine par le mot class et un point-virgule, c'est-à-dire avec la forme :

type  className = class;

sans ancêtre, ni liste des membres de classe après le mot class : c'est une déclaration avancée. Une déclaration avancée doit être résolue par une déclaration de définition de la même classe, dans la même section de déclaration de classe. En d'autres termes, entre une déclaration avancée et sa déclaration de définition, il ne peut y avoir que d'autres déclarations de type.

Les déclarations avancées permettent de définir des classes mutuellement dépendantes. Par exemple :

type
  TFigure = class;   // forward declaration
  TDrawing = class
    Figure: TFigure;
    // ...
  end;
 
  TFigure = class   // defining declaration
    Drawing: TDrawing;
    // ...
  end;

Ne confondez pas les déclarations avancées avec les déclarations complètes de types qui dérivent de System.TObject sans déclarer de membres de classe.

type
  TFirstClass = class;   // this is a forward declaration
  TSecondClass = class   // this is a complete class declaration
  end;                   //
  TThirdClass = class(TObject);  // this is a complete class declaration

Voir aussi