Programmierkurs: Delphi: Pascal: Interfaces

Aus Wikibooks
Zur Navigation springen Zur Suche springen

Interfaces[Bearbeiten]

Interfaces – erfahrene Programmierer, die bereits in anderen Sprachen programmiert haben, wissen eventuell schon, was Interfaces sind, Anfänger (und eventuell auch fortgeschrittene Programmierer) dagegen oftmals nicht.

Die Interfaces (zu deutsch: Schnittstellen) dienen der mehrfachen Vererbung von Eigenschaften und Methoden. Dabei wird jedoch eine vereinfachte Form verwendet, bei der das Interface nur angibt, welche Eigenschaften und Methoden in einer Klasse vorhanden sein müssen. Wie diese Methoden letztendlich arbeiten und welche Werte die Eigenschaften erhalten, wird in jedem Falle von der erbenden Klasse implementiert.

Das Ziel der Interfaces liegt darin, dass alle Objekte, die ein bestimmtes Interface implementieren, immer gleich behandelt werden können, da alle vom Interface geforderten Methoden und Eigenschaften vorhanden sein müssen.

Interfaces werden daher gern – wie der Name schon sagt – als Schnittstellen zu anderen Programmen oder Komponenten verwendet. Man kann damit ein Programm modularisieren, also erweiterbar gestalten, indem man in der Anwendung ein Interface anbietet und zusätzliche Module (so genannte Plugins) dann dieses Interface umsetzen und die gewünschten Daten liefern.

Deklaration[Bearbeiten]

Ein Interface wird ähnlich wie eine Klasse deklariert. Bevor wir uns jedoch die Deklaration genauer ansehen, gibt es ein paar Regeln, die hierbei eingehalten werden müssen:

  • Zunächst sind alle Elemente eines Interfaces automatisch als public deklariert, andere Sichtbarkeiten (private, protected usw.) sind nicht erlaubt.
  • Weiterhin sind alle Methoden abstrakt, da das Interface selbst diese nicht implementiert. Das Schlüsselwort abstract darf somit nicht angegeben werden, andere Schlüsselwörter wie virtual oder override, die sich auf die Vererbung auswirken, funktionieren ebenfalls nicht.
  • Ein Interface darf keine Felder enthalten.
  • Aus dem letzten Punkt ergibt sich, dass der Zugriff auf Eigenschaften über Methoden erfolgen muss.
  • Schnittstellen enthalten keine Konstruktoren und Destruktoren.

Ein einfaches Interface wird nun wie folgt deklariert:

type
  IInterfaceName = interface(Vorfahr)
      ['{GUID}']
      function TuIrgendwas: Typ;
      function LiesEigenschaft: Typ;
      procedure SchreibeEigenschaft(NeuerWert: Typ);
      property Eigenschaft: Typ read LiesEigenschaft write SchreibeEigenschaft;
    end;

Diese Einrückung (Methoden, Eigenschaften und das abschließende end jeweils 1 Tab weiter als bei Klassen) ist so vorgesehen, um Interfaces schneller von Klassen im Quelltext unterscheiden zu können. Oftmals wird auch die gleiche Einrückung wie bei Klassen verwendet, was auch nicht falsch ist. Welche der beiden Varianten Sie verwenden, sei Ihrem Geschmack überlassen.

Interfaces können ebenfalls von anderen Interfaces abgeleitet werden. Wenn Sie (Vorfahr) weglassen, leitet sich Ihre Schnittstelle jedoch automatisch von IInterface ab.

Für eine bessere Übersichtlichkeit sollten Sie den Namen von Interface-Typen immer mit „I“ (dem Großbuchstaben „i“) beginnen.

Die Zeile ['{GUID}'] müssen Sie ebenfalls nicht angeben. Sie dient dazu, die Schnittstelle eindeutig zu identifizieren. GUID bedeutet „Globally Unique Identifier“ und stellt eine weltweit eindeutige Hexadezimalzahl mit 128 Bit dar. Bei der Deklaration eines Interfaces müssen Sie die Textdarstellung über 36 Zeichen verwenden. Am einfachsten erzeugen Sie solch eine GUID in Delphi mit der Tastenkombination Strg+Umsch+G. Wenn Sie eine COM-fähige Anwendung zur Kommunikation mit anderen Anwendungen schreiben wollen, ist die GUID zwingend erforderlich.

Anwendung[Bearbeiten]

Sie können das Interface wie einen eigenen Datentyp verwenden, also Variablen und Funktionsparameter vom Typ eines Interfaces deklarieren. Dieser Variablen können Sie dann nur Objekte (also mittels Create erstellte Instanzen einer Klasse) übergeben, die dieses Interface implementieren. Über die Variable wiederum können Sie nur auf die Methoden und Eigenschaften zugreifen, die in dem entsprechenden Interface enthalten sind.

Um ein Interface in einer Klasse zu implementieren, geben Sie es als Vorfahr der Klasse an. Da alle Interfaces von IInterface abstammen, enthalten sie die Methoden QueryInterface, _AddRef und _Release. Diese werden bereits in der Klasse TInterfacedObject implementiert, daher sollten Sie alle Klassen, die ein Interface umsetzen, von dieser Klasse ableiten.

Beispiel[Bearbeiten]

Als Beispiel wollen wir uns einmal eine Tankstelle ansehen. Zunächst erstellen wir ein Interface mit einer Methode, die von allen Tankstellen angeboten werden muss, nämlich die Preisberechnung:

type
  ITankstelle = interface
      function BerechnePreis(Liter: Single): Single;
    end;

Nun benötigen wir noch zwei Tankstellen, die sich von dieser Schnittstelle ableiten:

  TErsteTankstelle = class(TInterfacedObject, ITankstelle)
    function BerechnePreis(Liter: Single): Single;
  end;

  TZweiteTankstelle = class(TInterfacedObject, ITankstelle)
    function BerechnePreis(Liter: Single): Single;
  end;

function TErsteTankstelle.BerechnePreis(Liter: Single): Single;
begin
  Result := 1.5 * Liter;
end;

function TZweiteTankstelle.BerechnePreis(Liter: Single): Single;
begin
  Result := 1.3 * Liter;
end;

Beide Tankstellen haben einen anderen Preis. Damit wir nun einheitlich und unabhängig von der tatsächlich benutzten Tankstelle abfragen können, wie hoch der Preis bei einem bestimmten Tankvolumen ist, verwenden wir eine Hilfsroutine, die das Volumen und die Tankstelle entgegen nimmt:

function HoleTankpreis(Liter: Single; Tankstelle: ITankstelle): Single;
begin
  try
    Result := Tankstelle.BerechnePreis(Liter);
  except
    Writeln('Die Tankstelle wurde nicht gefunden');
    Result := 0;
  end;
end;

Mit dem try...except Block werden Laufzeitfehler (Exceptions) abgefangen. Hierzu erfahren Sie im nächsten Kapitel mehr.

Wir können nun immer diese Funktion verwenden, wenn wir den Gesamttankpreis erfahren möchten. Um das Beispiel zu vervollständigen, benötigen wir noch zwei Tankstellenobjekte:

var
  Tankstelle1: TErsteTankstelle;
  Tankstelle2: TZweiteTankstelle;

begin
  Tankstelle1 := TErsteTankstelle.Create;
  Tankstelle2 := TZweiteTankstelle.Create;
  Writeln(HoleTankpreis(50, Tankstelle1));
  Writeln(HoleTankpreis(50, Tankstelle2));
  Readln;
end.


Arrow left.png Pascal: Zugriff auf Klassen Inhaltsverzeichnis Pascal: Exceptions Arrow right.png