Embarcadero Delphi
interfacce
Ricerca…
Osservazioni
Le interfacce sono utilizzate per descrivere le informazioni necessarie e l'output previsto di metodi e classi, senza fornire informazioni sull'implementazione esplicita.
Le classi possono implementare interfacce e le interfacce possono ereditare l' una dall'altra. Se una classe sta implementando un'interfaccia, significa che tutte le funzioni e le procedure esposte dall'interfaccia esistono nella classe.
Un aspetto speciale delle interfacce in delphi è che le istanze delle interfacce hanno una gestione a vita basata sul conteggio dei riferimenti. La durata delle istanze di classe deve essere gestita manualmente.
Considerando tutti questi aspetti, le interfacce possono essere utilizzate per raggiungere diversi obiettivi:
- Fornire molteplici implementazioni diverse per le operazioni (ad esempio il salvataggio in un file, database o invio come e-mail, tutto come interfaccia "SaveData")
- Riduci le dipendenze, migliorando il disaccoppiamento e rendendo il codice più gestibile e verificabile
- Lavora con le istanze in più unità senza essere turbato dalla gestione a vita (anche se qui esistono insidie, fai attenzione!)
Definizione e implementazione di un'interfaccia
Un'interfaccia è dichiarata come una classe, ma senza modificatori di accesso ( public
, private
, ...). Inoltre, non sono consentite definizioni, quindi non è possibile utilizzare variabili e costanti.
Le interfacce devono sempre avere un identificatore univoco , che può essere generato premendo Ctrl + Maiusc + G.
IRepository = interface
['{AFCFCE96-2EC2-4AE4-8E23-D4C4FF6BBD01}']
function SaveKeyValuePair(aKey: Integer; aValue: string): Boolean;
end;
Per implementare un'interfaccia, il nome dell'interfaccia deve essere aggiunto dietro la classe base. Inoltre, la classe dovrebbe essere un discendente di TInterfacedObject
(questo è importante per la gestione della durata ).
TDatabaseRepository = class(TInterfacedObject, IRepository)
function SaveKeyValuePair(aKey: Integer; aValue: string): Boolean;
end;
Quando una classe implementa un'interfaccia, deve includere tutti i metodi e le funzioni dichiarati nell'interfaccia, altrimenti non verrà compilata.
Una cosa degna di nota è che i modificatori di accesso non hanno alcuna influenza, se il chiamante lavora con l'interfaccia. Ad esempio, tutte le funzioni dell'interfaccia possono essere implementate come membri strict private
, ma possono comunque essere chiamate da un'altra classe se viene utilizzata un'istanza dell'interfaccia.
Implementazione di più interfacce
Le classi possono implementare più di una interfaccia, anziché ereditare da più di una classe ( Multiploe ereditarie ) che non è possibile per le classi Delphi. Per ottenere ciò, il nome di tutte le interfacce deve essere aggiunto separato da virgole dietro la classe base.
Naturalmente, la classe di implementazione deve anche definire le funzioni dichiarate da ciascuna delle interfacce.
IInterface1 = interface
['{A2437023-7606-4551-8D5A-1709212254AF}']
procedure Method1();
function Method2(): Boolean;
end;
IInterface2 = interface
['{6C47FF48-3943-4B53-8D5D-537F4A0DEC0D}']
procedure SetValue(const aValue: TObject);
function GetValue(): TObject;
property Value: TObject read GetValue write SetValue;
end;
TImplementer = class(TInterfacedObject, IInterface1, IInterface2)
// IInterface1
procedure Method1();
function Method2(): Boolean;
// IInterface2
procedure SetValue(const aValue: TObject);
function GetValue(): TObject
property Value: TObject read GetValue write SetValue;
end;
Ereditarietà per le interfacce
Le interfacce possono ereditare l'una dall'altra, esattamente come fanno le classi. Una classe di implementazione deve quindi implementare le funzioni dell'interfaccia e tutte le interfacce di base. In questo modo, tuttavia, il compilatore non sa che la classe di implanto implementa anche l'interfaccia di base, conosce solo le interfacce che sono esplicitamente elencate. Ecco perché l'utilizzo as ISuperInterface
su TImplementer
non funzionerebbe. Ciò comporta anche la pratica comune, di implementare esplicitamente anche tutte le interfacce di base (in questo caso TImplementer = class(TInterfacedObject, IDescendantInterface, ISuperInterface)
).
ISuperInterface = interface
['{A2437023-7606-4551-8D5A-1709212254AF}']
procedure Method1();
function Method2(): Boolean;
end;
IDescendantInterface = interface(ISuperInterface)
['{6C47FF48-3943-4B53-8D5D-537F4A0DEC0D}']
procedure SetValue(const aValue: TObject);
function GetValue(): TObject;
property Value: TObject read GetValue write SetValue;
end;
TImplementer = class(TInterfacedObject, IDescendantInterface)
// ISuperInterface
procedure Method1();
function Method2(): Boolean;
// IDescendantInterface
procedure SetValue(const aValue: TObject);
function GetValue(): TObject
property Value: TObject read GetValue write SetValue;
end;
Proprietà nelle interfacce
Poiché la dichiarazione delle variabili nelle interfacce non è possibile, non è possibile utilizzare il metodo "veloce" di definizione delle proprietà corrette ( property Value: TObject read FValue write FValue;
). Invece, Getter e setter (ciascuno solo se necessario) devono essere dichiarati nell'interfaccia, anche.
IInterface = interface(IInterface)
['{6C47FF48-3943-4B53-8D5D-537F4A0DEC0D}']
procedure SetValue(const aValue: TObject);
function GetValue(): TObject;
property Value: TObject read GetValue write SetValue;
end;
Una cosa degna di nota è che la classe di implementazione non deve dichiarare la proprietà. Il compilatore accetterebbe questo codice:
TImplementer = class(TInterfacedObject, IInterface)
procedure SetValue(const aValue: TObject);
function GetValue(): TObject
end;
Un avvertimento, tuttavia, è che in questo modo è possibile accedere alla proprietà solo attraverso un'istanza dell'interfaccia, noth attraverso la classe stessa. Inoltre, l'aggiunta della proprietà alla classe aumenta la leggibilità.