Embarcadero Delphi
Schnittstellen
Suche…
Bemerkungen
Schnittstellen werden verwendet, um die benötigten Informationen und die erwartete Ausgabe von Methoden und Klassen zu beschreiben, ohne Informationen über die explizite Implementierung bereitzustellen.
Klassen können Schnittstellen implementieren , und Schnittstellen können voneinander erben . Wenn eine Klasse eine Schnittstelle implementiert , bedeutet dies, dass alle von der Schnittstelle bereitgestellten Funktionen und Prozeduren in der Klasse vorhanden sind.
Ein besonderer Aspekt von Interfaces in Delphi ist, dass Instanzen von Interfaces über ein Lebensdauermanagement verfügen, das auf der Referenzzählung basiert. Die Lebensdauer von Klasseninstanzen muss manuell verwaltet werden.
Unter Berücksichtigung all dieser Aspekte können Schnittstellen verwendet werden, um verschiedene Ziele zu erreichen:
- Mehrere verschiedene Implementierungen für Operationen bereitstellen (z. B. Speichern in einer Datei, Datenbank oder Senden als E-Mail, alle als Schnittstelle "SaveData")
- Reduzieren Sie Abhängigkeiten, verbessern Sie die Entkopplung und machen Sie so den Code besser wartbar und überprüfbar
- Arbeiten Sie mit Instanzen in mehreren Einheiten, ohne sich durch das Lifetime-Management zu stören (obwohl auch hier Fallstricke vorhanden sind, passen Sie auf!)
Schnittstelle definieren und implementieren
Eine Schnittstelle wird wie eine Klasse deklariert, jedoch ohne Zugriffsmodifizierer ( public
, private
, ...). Außerdem sind keine Definitionen zulässig, daher können Variablen und Konstanten nicht verwendet werden.
Schnittstellen sollten immer über eine eindeutige Kennung verfügen, die durch Drücken von Strg + Umschalttaste + G generiert werden kann.
IRepository = interface
['{AFCFCE96-2EC2-4AE4-8E23-D4C4FF6BBD01}']
function SaveKeyValuePair(aKey: Integer; aValue: string): Boolean;
end;
Um eine Schnittstelle zu implementieren, muss der Name der Schnittstelle hinter der Basisklasse hinzugefügt werden. Außerdem sollte die Klasse ein Nachkomme von TInterfacedObject
(dies ist wichtig für die Lebenszeitverwaltung ).
TDatabaseRepository = class(TInterfacedObject, IRepository)
function SaveKeyValuePair(aKey: Integer; aValue: string): Boolean;
end;
Wenn eine Klasse eine Schnittstelle implementiert, muss sie alle in der Schnittstelle deklarierten Methoden und Funktionen enthalten. Andernfalls wird sie nicht kompiliert.
Bemerkenswert ist, dass Zugriffsmodifizierer keinen Einfluss haben, wenn der Anrufer mit der Schnittstelle arbeitet. Zum Beispiel können alle Funktionen der Schnittstelle als strict private
Member implementiert werden, sie können jedoch auch von einer anderen Klasse aufgerufen werden, wenn eine Instanz der Schnittstelle verwendet wird.
Implementierung mehrerer Schnittstellen
Klassen können mehr als eine Schnittstelle implementieren, anstatt von mehreren Klassen zu erben ( Multiple Inheritance ), was für Delphi-Klassen nicht möglich ist. Um dies zu erreichen, muss der Name aller Schnittstellen durch Kommas getrennt hinter der Basisklasse hinzugefügt werden.
Natürlich muss die implementierende Klasse auch die von jeder der Schnittstellen deklarierten Funktionen definieren.
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;
Vererbung für Schnittstellen
Schnittstellen können voneinander erben, genau wie Klassen auch. Eine implementierende Klasse muss also Funktionen der Schnittstelle und aller Basisschnittstellen implementieren. Auf diese Weise weiß der Compiler jedoch nicht, dass die implizierende Klasse auch die Basisschnittstelle implementiert. Er kennt nur die explizit aufgeführten Schnittstellen. Aus diesem Grund würde die Verwendung as ISuperInterface
auf TImplementer
nicht funktionieren. Dies führt in der üblichen Praxis auch dazu, alle Basisschnittstellen explizit zu implementieren (in diesem Fall 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;
Eigenschaften in Schnittstellen
Da die Deklaration von Variablen in Schnittstellen nicht möglich ist, kann die "schnelle" Art der Definition von Eigenschaften ( property Value: TObject read FValue write FValue;
) nicht verwendet werden. Stattdessen müssen auch Getter und Setter (jeweils nur bei Bedarf) in der Schnittstelle deklariert werden.
IInterface = interface(IInterface)
['{6C47FF48-3943-4B53-8D5D-537F4A0DEC0D}']
procedure SetValue(const aValue: TObject);
function GetValue(): TObject;
property Value: TObject read GetValue write SetValue;
end;
Bemerkenswert ist, dass die implementierende Klasse die Eigenschaft nicht deklarieren muss. Der Compiler würde diesen Code akzeptieren:
TImplementer = class(TInterfacedObject, IInterface)
procedure SetValue(const aValue: TObject);
function GetValue(): TObject
end;
Ein Nachteil ist jedoch, dass auf diese Eigenschaft nur über eine Instanz der Schnittstelle zugegriffen werden kann, nicht über die Klasse selbst. Durch das Hinzufügen der Eigenschaft zur Klasse wird außerdem die Lesbarkeit erhöht.