Einzelnen Beitrag anzeigen

Lemmy

Registriert seit: 8. Jun 2002
Ort: Berglen
2.379 Beiträge
 
Delphi 10.3 Rio
 
#11

AW: Schon wieder: Warum Interfaces

  Alt 20. Okt 2016, 10:03
Ich finde, Interface-Programmierung verhält sich zu OOP wie die OOP zur prozeduralen Programmierung.

Grundsätzlich kannst Du jedes Problem auch ohne Interfaces und ohne OOP lösen. Und keinen interessiert es. Allerdings handelst Du dir immer irgend welche Probleme ein, in der Fachwelt wird dafür der Begriff der technischen Schulden verwendet. Keiner sieht sie, man riecht sie nicht und eines Tages schwappen sie über dich

Genauso wenig wie

Delphi-Quellcode:
type
  TFoo = class(TObject)
  public
    procedure DoFoo; //mit 1.000 Zeile Code
  end;
objektorientierte Programmierung ist, ist die Definition eines Interfaces, die Implementierung und Verwendung davon automatisch auch eine sinnvolle Verwendung eines Interfaces, weil es oft schlicht und ergreifend nur mehr Arbeit ist aber keinen Vorteil bietet, weil man die Technik falsch anwendet bzw. im falschen Kontext.

Interfaces sind eine logische Weiterführung der OOP, vielleicht müsste man auch sagen, eine zwingende Weiterführung. Aber es ist wie gesagt nicht damit getan ein

Delphi-Quellcode:
type
  IFoo = Interface
    procedure DoFoo;
  end;
zu schreiben und zu Jubeln "Ich kann Interfaces", sondern dann folgen automatisch auch weiter Punkte die dann wichtig werden, denn Interfaces werfen wie vieles andere auch, mehr Fragen auf, als sie beantworten Als ein Stichpunkt werfe ich hier nur mal Dependency Injection ein.


Der große Vorteil von Interfaces eröffnet sich erst in etwas komplexeren Systemen, in denen div. Klassen miteinander kommunizieren müssen. Hier hast Du die Möglichkeit, dass diese Klassen weiterhin miteinander arbeiten könne, sich aber gegenseitig nicht mehr persönlich kennen müssen:

Delphi-Quellcode:
TAdresse = class()
...
public
  function GetAnrede: String;
  function GetName1: String;
  function GetName2: String;
  function GetStrasse: String;
....
end;

TPerson = class()
private
  FName, FVOrname: String;
  FStrasse: String;
....
public
  property Adresse: TAdresse read FAdresse;
Und damit wir einen Konsumenten haben:

Delphi-Quellcode:
TBrief = class()
public
  procedure ErstelleBrief(Empfaenger: TAdresse);
end;
hier hast Du einen harte Kopplung wie sie in div. Projekten vorkommt und alles ist OK.
Willst Du jetzt aber TBrief durch einen Unittest jagen hast Du das Problem, dass Du zwingend auch eine Instanz von TAdresse erzeugen und übergeben musst. Kein Problem, solange TAdresse keine weiteren Abhängigkeiten hat. Sobald das System und das BOM komplexer werden, wirst Du hier immer mehr abhängige Klassen vorbereiten müssen, instanziieren müssen bis du zu dem Punkt kommst: Unittests sind scheiße, weil Du für eine Zeile Testcode, 10, 20 Zeilen Code für die Instanziierung und für die Aufräumaktionen brauchst. Und du merkst nicht, dass eigentlich dein Modell ein Problem hat.

Mit Interfaces sieht das ganze so aus:

Delphi-Quellcode:
IAdresse = Interface
  function GetAnrede: String;
  function GetName1: String;
  function GetName2: String;
  function GetStrasse: String;
end;

TPerson = class()
private
  FName, FVOrname: String;
  FStrasse: String;
....
public
  property Adresse: IAdresse read FAdresse;
Und damit wir einen Konsumenten haben:

Delphi-Quellcode:
TBrief = class()
public
  procedure ErstelleBrief(Empfaenger: IAdresse);
end;
Nun kann TPerson selbst entscheiden, welche Implementierung von IAdresse (es kann mehrere geben) für seinen Zwecke am besten ist. TBrief ist das aber völlig egal, durch das Interface weiß die Klasse, dass die vertraglich vereinbarten Methoden vorhanden sind und funktionieren, d.h. Du kannst TBrief dann auch damit verwenden:

Delphi-Quellcode:
type
  TStudent = class(X,IAdresse)
  ...
  public
    function GetAnrede: String;
    function GetName1: String;
    function GetName2: String;
    function GetStrasse: String;
und TStudent muss kein weiteres Element deines TPerson-Frameworks kennen, muss nicht von einer gemeinsamen Basisklasse abgeleitet werden,....


Kommen wir aber nochmal zurück zur TPerson: ich habe oben geschrieben, dass TPerson entscheidet welche Implementierung von IAdresse es verwendet. Das ist aber schon wieder ein Fehler! Weil du baust dir hier wieder über die Hintertür eine harte Kopplung von 2 Klassen ein. Und genau das kann später wieder zu einem Problem werden, weil du dann an dem Punkt bis: Verflixt, jetzt wollte ich harte Kopplung vermeinden, muss aber halt irgend wo implementieren:

Delphi-Quellcode:
TPerson = class()
private
  FName, FVOrname: String;
  FStrasse: String;
....
public
  property Adresse: IAdresse read FAdresse;

...

TPerson.Create()
begin
  FAdresse := TFooAdresse.Create();
end;
Lösung für dieses Problem bietet die Dependency Injection: Ich kopple das Interface nicht an einer bestimmten Stelle mit einer konkreten Implementierung sondern biete über eine Liste (sog. Container) verschiedene Möglichkeiten an (oder vielleicht auch nur eine) wie IAdresse implementiert sein soll. TPerson instanziiert dann die IAdresse nicht selbst, sondern fragt bei der Liste an: Gib mir eine Instanz von IAdresse. Aber den Part könnte Stefan G. nun wirklich besser erklären

wie auch bei der OOP ist auch bei Interfaces das Problem: Mit 2 Sätzen und einem einfachen Beispiel ist das nicht erklärt....

Geändert von Lemmy (20. Okt 2016 um 10:52 Uhr)
  Mit Zitat antworten Zitat