Hallo,
Der einzige Grund, wieso ich bisher bewusst ein Interface eingesetzt habe (von
COM und sowas mal abgesehen), war, dass ich Mehrfachvererbung für eine Klasse brauchte.
Was du meins ist, du brauchtest eine Mehrfachschnittstellenvererbung. Die ist ja auch relativ einfach umzusetzen.
Ist mir bis heute unverständlich, wieso Delphi sowas nicht unterstützt.
Was du jetzt meins ist die Mehrfachverhaltensvererbung, welche leider nicht so trivial ist.
Ich finde, Interface-Programmierung verhält sich zu
OOP wie die
OOP zur prozeduralen Programmierung.
Jetzt fällt mir sofort "
Objekt
orientierte
Softwareentwicklung" aus Unizeiten ein. Was hat uns der OOS-Vorleser immer an den Kopf geworfen.
Zitat:
OOP ist nur syntaktischer Zucker.
Ja und da haben wir mit einer nicht
OOP-fähigen Programmiersprache
OOP-Programmiert und somit das Konzept hinter
OOP kennen gelernt.
Eine Klasse besteht aus einer Schnittstelle und deren Implementierung. Eine Klasse kann die Schnittstelle zusammen mit deren Implementierung einer anderen Klasse erben. Und das Vererben der Implementierung ist
OOP. Ein Interface ist somit quasi eine halbe Klasse, nämlich die Schnittstelle. Der Vorteil von
OOP ist die Codereduzierung. Ohne
OOP müsste man bei einem TNumberEdit größtenteils das komplette TEdit bis runter zu TObject nochmal neu implementieren. Mit
OOP ist das nur eine klitzekleine Vererbung mit dem überschreiben einer virtuellen Methode. Interfaces bringen keine weitere Codereduzierung. Falsch eingesetzt erhöhen sie sogar die Menge an Code.
Und hiermit stelle ich an die Interface-Prediger mal eine Aufgabe. Baut doch mal TNumberEdit, TEdit bis runter zu TObject Interface-like nach ohne Klassenvererbung und ohne die Klassen aus
VCL/
RTL einzusetzen. Aber bitte mit Implementierung. Mich würde mal interessieren wie das denn aussehen würde.
Interface-Programmierung kann sich nicht zu
OOP verhalten, weil sie teil dieser ist.
Grundsätzlich kannst Du jedes Problem auch ohne Interfaces und ohne
OOP lösen.
Ja, aber auch ohne Hochsprache und Assembler.
Und keinen interessiert es.
Doch, denn das wie ist entscheidend. Die Vorteile der Hochsprachen haben dazu geführt das sie sich durchgesetzt haben. Bei
OOP ist es das gleiche.
Genauso wenig wie
Delphi-Quellcode:
type
TFoo = class(TObject)
public
procedure DoFoo; //mit 1.000 Zeile Code
end;
objektorientierte Programmierung ist,
Wieso ist das kein
OOP. Selbst ein schlichtes
Delphi-Quellcode:
programm Test;
uses
System.SysUtils;
var
X: TBytes;
begin
X:= TEncoding.UTF8.GetBytes('Test');
end;
ist
OOP, wegen dem
TEncoding.UTF8.GetBytes
.
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.
Das habe ich jetzt auch nach mehrmals lesen nicht verstanden.
Interfaces sind eine logische Weiterführung der
OOP, vielleicht müsste man auch sagen, eine zwingende Weiterführung.
Nein. Interfaces sind Teil von
OOP.
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:
Das bekommst du aber auch ohne Interface hin. Ich sage nur reine abstrakte Klassen. Die haben mit Interfaces was gemeinsam. Denen fehlt auch die Implementierung der Schnittstelle.
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.
Was hindert dich daran erst mal eine rein abstrakte klasse TBasisAdresse zu definieren. Dann kannst du im Unittest eine schicke TUnittestAdresse bauen. Und beim Erzeugen von Objekten helfen Klassenreferenzen, die richtige Klasse zu verwenden. Und sollte es auch noch nach Jahren richtig sein, das bei allen Kindern ein gewisser Teil der Implementierung gleich ist, kann dieser ruhig in der Basis-Klasse verweilen und braucht somit nur einmal Implementiert und einmal gewartet werden.
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,....
Und du musst in jeder Klasse die Implementierung komplett neu hinschreiben, ob wohl sie bestimmt zu 90% identisch sind.
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
Um diese Abhängigkeiten aufzulösen brauchst du aber nicht zwingend Interfaces. Das geht auch mit Klassenreferenzen.
wie auch bei der
OOP ist auch bei Interfaces das Problem: Mit 2 Sätzen und einem einfachen Beispiel ist das nicht erklärt....
Ja, die Entscheidung Interfaces einzusetzen ist nicht einfach. Denn sie haben auch Nachteile.
Ich verwende auch Interfaces, aber sparsam. Bei mir ist das Verhältnis zwischen Interfaces und reiner Klassenvererbung gefühlt gleich dem in der
VCL/
RTL. Eigentlich benutze ich Interfaces nur da wo ich bei gemeinsamer Schnittstelle keine gemeinsame Vererbungslinie aufbauen kann. Für dieses Problem wünsche ich mir aber eine andere Lösung. Und jetzt mal ein Beispiel wie die andere Lösung aussehen müsste und wo Interfaces absolut nicht helfen.
Delphi-Quellcode:
Type
TCustomLabeledControl<T: TControl>= class(T)
strict private
fLabel: TLabel;
strict protected
function GetLabelControlClass: TLabelClass; virtual;
protected
// Und viele override
public
constructor Create(AOwner: TComponent); override;
procedure SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer; AHeight: Integer); override;
// Und noch mehr override
end;
TLabeledEdit= class(TCustomLabeledControl<TEdit>);
TLabeledComboBox= class(TCustomLabeledControl<TComboBox>);
TLabeledMemo= class(TCustomLabeledControl<TMemo>);
TLabeledRichEdit= class(TCustomLabeledControl<TRichEdit>);
implementation
constructor TCustomLabeledControl<T: TControl>.Create(AOwner: TComponent);
begin
inherited;
fLabel:= GetLabelControlClass.Create(nil); // Oder auch Self wie man mag
end;
function TCustomLabeledControl<T: TControl>.GetLabelControlClass: TLabelClass;
begin
Result:= TLabeledControlManager.Instance.GetLabelControlClass(…); // Einen String, Enum, Self oder was auch immer passent ist übergeben
end;
procedure TCustomLabeledControl<T: TControl>.SetBounds(ALeft: Integer; ATop: Integer; AWidth: Integer; AHeight: Integer);
begin
inherited;
fLabel.SetBounds(…);
end;
einbeliebigername.