Implémentation des interfaces

De Appmethod Topics
Aller à : navigation, rechercher

Remonter à Interfaces d'objets - Index

Une fois une interface déclarée, elle doit être implémentée par une classe avant de pouvoir être utilisée. Les interfaces implémentées par une classe sont spécifiées dans la déclaration de la classe après le nom de l'ancêtre de la classe.

Déclarations de classes

De telles déclarations ont la forme :

type className = class (ancestorClass, interface1, ..., interfaceN)
	memberList
end;

Par exemple :

type
  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)
    // ...
  end;

déclare une classe appelée TMemoryManager qui implémente les interfaces IMalloc et IErrorInfo. Quand une classe implémente une interface, elle doit implémenter (ou hériter d'une implémentation de) chaque méthode déclarée dans l'interface.

Voici la déclaration de System.TInterfacedObject :

type
 TInterfacedObject = class(TObject, IInterface)
 protected
   FRefCount: Integer;
   function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
   function _AddRef: Integer; stdcall;
   function _Release: Integer; stdcall;
 public
   procedure AfterConstruction; override;
   procedure BeforeDestruction; override;
   class function NewInstance: TObject; override;
   property RefCount: Integer read FRefCount;
 end;

TInterfacedObject implémente l'interface IInterface. Donc TInterfacedObject déclare et implémente chacune des trois méthodes de IInterface.

Les classes qui implémentent des interfaces peuvent également s'utiliser comme classes de base. (Le premier exemple ci-dessus déclare TMemoryManager comme descendant direct de TInterfacedObject.) Chaque interface hérite de IInterface, et une classe qui implémente des interfaces doit implémenter les méthodes QueryInterface, _AddRef et _Release. TInterfacedObject de l'unité System implémente ces méthodes et fournit donc une base pratique pour dériver des classes qui implémentent des interfaces.

Quand une interface est implémentée, chacune de ses méthodes est mappée sur une méthode de la classe d'implémentation ayant le même type de résultat, la même convention d'appel, le même nombre de paramètres et ayant à chaque position le même type de paramètre. Par défaut, chaque méthode d'interface est mappée à une méthode de même nom dans la classe d'implémentation.

Clause de résolution de méthode

Vous pouvez redéfinir les mappages par défaut basés sur les noms en incluant des clauses de résolution de méthode dans la déclaration d'une classe. Quand une classe implémente plusieurs interfaces ayant des méthodes portant le même nom, les clauses de résolution de méthode vous permettent d'éviter les conflits de nom.

Une clause de résolution de méthode a la forme suivante :

procedure interface.interfaceMethod = implementingMethod;

ou :

function interface.interfaceMethod = implementingMethod;

implementingMethod est une méthode déclarée dans la classe ou dans l'un de ses ancêtres. implementingMethod peut être une méthode déclarée plus loin dans la déclaration de classe, mais ce ne peut pas être une méthode privée d'une classe ancêtre déclarée dans un autre module.

Par exemple, la déclaration de classe :

type
  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)
    function IMalloc.Alloc = Allocate;
    procedure IMalloc.Free = Deallocate;
	// ...
  end;

mappe les méthodes Alloc et Free de IMalloc sur les méthodes Allocate et Deallocate de TMemoryManager.

Une clause de résolution de méthode ne peut changer un mappage introduit par une classe ancêtre.

Modification des implémentations héritées

Les classes descendantes peuvent changer la manière dont est implémentée une méthode d'interface spécifique en redéfinissant la méthode d'implémentation. Il est nécessaire pour ce faire que la méthode d'implémentation soit virtuelle ou dynamique.

Une classe peut également réimplémenter la totalité d'une interface qu'elle hérite d'une classe ancêtre. Cela nécessite de spécifier à nouveau l'interface dans la déclaration de la classe descendante. Par exemple :

type
  IWindow = interface
    ['{00000115-0000-0000-C000-000000000146}']
    procedure Draw;
    // ...
  end;
  TWindow = class(TInterfacedObject, IWindow)
    // TWindow implements IWindow pocedure Draw;
    // ...
  end;
  TFrameWindow = class(TWindow, IWindow)
    // TFrameWindow reimplements IWindow procedure Draw;
    // ...
  end;

La réimplémentation d'une interface masque l'implémentation héritée de la même interface. Dans ce cas, les clauses de résolution de méthode d'une classe ancêtre ne s'appliquent pas à l'interface réimplémentée.

Implémentation des interfaces par délégation

La directive implements vous permet de déléguer l'implémentation d'une interface à une propriété de la classe d'implémentation. Par exemple :

property MyInterface: IMyInterface read FMyInterface implements IMyInterface;

déclare une propriété appelée MyInterface qui implémente l'interface IMyInterface.

La directive implements doit être le dernier spécificateur dans la déclaration de la propriété, elle peut énumérer plusieurs interfaces, séparées par des virgules. La propriété déléguée :

  • Doit être de type classe ou interface.
  • Ne peut pas être une propriété tableau, ni avoir de spécificateur d'index.
  • Doit avoir un spécificateur read. Si la propriété utilise une méthode read, cette méthode doit utiliser la convention d'appel par défaut register et elle ne peut pas être dynamique (par contre, elle peut être virtuelle) ou spécifier la directive message.

La classe que vous utilisez pour implémenter l'interface déléguée doit dériver de System.TAggregatedObject.

Délégation à une propriété de type interface

Si la propriété déléguée est de type interface, cette interface (ou une interface dont elle dérive) doit apparaître dans la liste des ancêtres de la classe dans laquelle la propriété est déclarée. La propriété déléguée doit renvoyer un objet dont la classe implémente complètement l'interface spécifiée par la directive implements et ce sans utiliser de clauses de résolution de méthode. Par exemple :

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;
  TMyClass = class(TObject, IMyInterface)
    FMyInterface: IMyInterface;
    property MyInterface: IMyInterface read FMyInterface implements IMyInterface;
  end;
var
  MyClass: TMyClass;
  MyInterface: IMyInterface;
begin
  MyClass := TMyClass.Create;
  MyClass.FMyInterface := ...// some object whose class implements IMyInterface
  MyInterface := MyClass;
  MyInterface.P1;
end;

Délégation à une propriété de type classe

Si la propriété déléguée est de type classe, les méthodes implémentant l'interface spécifiée sont d'abord recherchées dans la classe et ses ancêtres, puis dans la classe conteneur et dans ses ancêtres. Il est ainsi possible d'implémenter certaines méthodes dans la classe spécifiée par la propriété et d'autres dans la classe dans laquelle la propriété est déclarée. Il est possible d'utiliser de manière normale les 
clauses de résolution de méthode afin de résoudre les ambiguïtés ou de spécifier une méthode particulière. Une interface ne peut être implémentée par plus d'une propriété de type classe. Par exemple :

type
  IMyInterface = interface
    procedure P1;
    procedure P2;
  end;
  TMyImplClass = class
    procedure P1;
    procedure P2;
  end;
  TMyClass = class(TInterfacedObject, IMyInterface)
    FMyImplClass: TMyImplClass;
    property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
    procedure IMyInterface.P1 = MyP1;
    procedure MyP1;
  end;
procedure TMyImplClass.P1;
     // ...
procedure TMyImplClass.P2;
     // ...
procedure TMyClass.MyP1;
     // ...
var
  MyClass: TMyClass;
  MyInterface: IMyInterface;
begin
  MyClass := TMyClass.Create;
  MyClass.FMyImplClass := TMyImplClass.Create;
  MyInterface := MyClass;
  MyInterface.P1;	// calls TMyClass.MyP1;
  MyInterface.P2;	// calls TImplClass.P2;
end;

Voir aussi