AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Tutorial Interfaces

Tutorial Interfaces

Ein Tutorial von Fritzew · begonnen am 12. Apr 2017 · letzter Beitrag vom 11. Okt 2017
Antwort Antwort
Seite 3 von 5     123 45   
Fritzew
Registriert seit: 18. Nov 2015
Tutorial Interfaces

Warum dieses Tutorial?

Es ist mir aufgefallen das immer wieder Missversändnisse und Unklarheiten über den Nutzen und
Einsatz von Interfaces auftreten.
Deshalb habe ich mich Entschlossen das etwas aufzuhellen.
Dieses Tutorial beinhaltet natürlich meine Sicht darauf und ich lasse mich gerne verbessern.


Was ist ein Interface?

Ein Interface, übersetzt Schnittstelle, ist genau das was der Name uns mitteilt, es ist eine Schnittstelle zwischen verschiedenen
Programmteilen. Der Vorteil liegt in der Trennung von Definition und Implementation.
Ein CodeTeil der eine Schnittstelle benutzt muss nicht wissen wie diese Umgesetzt (Implementiert) ist.

Betrachten wir es wie eine USB Verbindung. Wir haben das Interface (den Stecker, die Buchse) und da können die verschiedensten
Geräte verbunden werden. Alles was jedes Ende wissen muss, ist wie über die Schnittstelle (Interface) kommuniziert wird.
Ob da nun ein Drucker, eine Kamera oder was auch immer daran hängt ist erst einmal unwichtig.

In diesem Tutorial gehe ich erst einmal auf eine recht einfache Anwendung von Interfaces ein.

Ich denke jeder kennt das Problem: Programmeinstellungen, Anwendereinstellunge etc. müssen zwischengespeichert werden, damit der
Anwender seine Einstellungen etc. bei jedem Programmstart wieder findet.

Die Lösung die fast jeder da gerne zumindest am Anfang verwendet sind Inifiles. Einfach zu benutzen und effektiv.
Irgendwann dann sollen die Einstellungen in eine Datenbank oder ähnliches geschrieben werden.
Es steht ein grösserer Umbau an.

Die Lösung? Ja genau Interfaces!

Das TCustomIniFile hat eigentlich schon alles was wir brauchen. Wir könnten einfach ein neue Klasse ableiten und diese benutzen.
Einfacher wird es aber wenn wir daraus den benötigten Teil in ein Interface auslagern und in unseren CodeTeilen dieses benutzen.

Das sieht dann z.B so aus:

Delphi-Quellcode:
   iConfigReadWriter = interface
      ['{AE9CC6E5-F0B8-4D39-8F6C-799423C60A37}']
      function SectionExists(const Section: string): Boolean;
      function ReadString(const Section, Ident, Default: string): string;
      procedure WriteString(const Section, Ident, Value: string);
      function ReadInteger(const Section, Ident: string; Default: Longint): Longint;
      procedure WriteInteger(const Section, Ident: string; Value: Longint);
      function ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
      procedure WriteBool(const Section, Ident: string; Value: Boolean);
      function ReadBinaryStream(const Section, Name: string; Value: TStream): Integer;
      function ReadDate(const Section, Name: string; Default: TDateTime): TDateTime;
      function ReadDateTime(const Section, Name: string; Default: TDateTime): TDateTime;
      function ReadFloat(const Section, Name: string; Default: Double): Double;
      function ReadTime(const Section, Name: string; Default: TDateTime): TDateTime;
      procedure WriteBinaryStream(const Section, Name: string; Value: TStream);
      procedure WriteDate(const Section, Name: string; Value: TDateTime);
      procedure WriteDateTime(const Section, Name: string; Value: TDateTime);
      procedure WriteFloat(const Section, Name: string; Value: Double);
      procedure WriteTime(const Section, Name: string; Value: TDateTime);
      procedure ReadSection(const Section: string; Strings: TStrings);
      procedure ReadSections(Strings: TStrings); overload;
      procedure ReadSections(const Section: string; Strings: TStrings); overload;
      procedure ReadSubSections(const Section: string; Strings: TStrings; Recurse: Boolean = False);
      procedure ReadSectionValues(const Section: string; Strings: TStrings);
      procedure EraseSection(const Section: string);
      procedure DeleteKey(const Section, Ident: string);
      function ValueExists(const Section, Ident: string): Boolean;
   end;
Anstatt einer Methode unserer Klassen mit der Signatur

procedure SaveConfig(Value : TcustomIniFile); können wir die Signatur ändern zu

procedure SaveConfig(Value : iConfigReadWriter);
für unseren restlichen Code ändert sich nichts da die Signaturen der Methoden gleich sind.

Value.WriteString('DATA','DUMP','Meine Daten'); Dies erfordert natürlich das wir die Erzeugung und Freigebe unserer "Schnittstelle" vom eigentlichen Code trennen.

Also anstatt einer methode:

Delphi-Quellcode:
procedure tuseini.BadSaveData;
var
   lini: TCustomIniFile;
begin
   lini := TIniFile.Create('Was auch immer');
   try
      lini.WriteString('Data', 'Dump', 'meine Daten');
  // etc
   finally
      lini.free;
   end;
end;
haben wir zumindest:

Delphi-Quellcode:
procedure tuseini.BetterSaveSettings(Writer: TCustomIniFile);
begin
   Writer.WriteString('Data', 'Dump', 'meine Daten');
end;

procedure tuseini.BetterSaveData;
var
   lini: TCustomIniFile;
begin
   lini := TIniFile.Create('Was auch immer');
   try
     BetterSaveSettings(lini);
   finally
      lini.free;
   end;
end;

Aus meiner Sicht ist dies die optimale Lösung:

Delphi-Quellcode:
procedure tuseini.SaveSettings(Writer: iConfigReadWriter);
begin
  Writer.WriteString('Test', 'Dummy', 'Default');
end;

procedure tuseini.ReadSettings(Reader: iConfigReadWriter);
begin
 fmySetting := Reader.ReadString('Test', 'Dummy', 'Empty');
end;
Unser Klasse muss nur das Interface kennen, was dahinter wirklich passiert ist nicht wichtig für die Benutzung.

Im Testprojekt ist jetzt auch ein Datamodul dass dieses Interface implementiert.
Es sind nicht alle Methoden befüllt sollte aber den zeigen wie man so einfach die Implementation ändern kann, ohne in der
benutzenden Klasse etwas zu ändern.

Delphi-Quellcode:
  TDataModule1 = class(TDataModule, iConfigReadWriter)
    FDConnection1: TFDConnection;
    FDGUIxWaitCursor1: TFDGUIxWaitCursor;
    FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink;
    procedure DataModuleDestroy(Sender: TObject);
    procedure DataModuleCreate(Sender: TObject);
  private
    procedure DeleteKey(const Section, Ident: string);
    procedure EraseSection(const Section: string);
    function ReadBinaryStream(const Section, Name: string; Value: TStream): Integer;
    function ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
    function ReadDate(const Section, Name: string; Default: TDateTime): TDateTime;
    function ReadDateTime(const Section, Name: string; Default: TDateTime): TDateTime;
    function ReadFloat(const Section, Name: string; Default: Double): Double;
    function ReadInteger(const Section, Ident: string; Default: Longint): Longint;
    procedure ReadSection(const Section: string; Strings: TStrings);
    procedure ReadSections(const Section: string; Strings: TStrings); overload;
    procedure ReadSections(Strings: TStrings); overload;
    procedure ReadSectionValues(const Section: string; Strings: TStrings);
    function ReadString(const Section, Ident, Default: string): string;
    procedure ReadSubSections(const Section: string; Strings: TStrings; Recurse: Boolean = False);
    function ReadTime(const Section, Name: string; Default: TDateTime): TDateTime;
    function SectionExists(const Section: string): Boolean;
    function ValueExists(const Section, Ident: string): Boolean;
    procedure WriteBinaryStream(const Section, Name: string; Value: TStream);
    procedure WriteBool(const Section, Ident: string; Value: Boolean);
    procedure WriteDate(const Section, Name: string; Value: TDateTime);
    procedure WriteDateTime(const Section, Name: string; Value: TDateTime);
    procedure WriteFloat(const Section, Name: string; Value: Double);
    procedure WriteInteger(const Section, Ident: string; Value: Longint);
    procedure WriteString(const Section, Ident, Value: string);
    procedure WriteTime(const Section, Name: string; Value: TDateTime);
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;


Dies habe ich im testProjekt über eine simple Factory gelöst.

Im Anhang findet Ihr ein Projekt das dies Umsetzt.

Bei Interesse kann ich auch noch weiter in die Interface problematik einsteigen, Supports, Delegeation etc..

Habe das Tutorial.zip erweitert mit einer DB Lösung
Angehängte Dateien
Dateityp: zip TutInterface.zip (7,8 KB, 114x aufgerufen)

Geändert von Fritzew (12. Apr 2017 um 13:19 Uhr)
 
Benutzerbild von DeddyH
DeddyH

 
Delphi 12 Athens
 
#21
  Alt 12. Apr 2017, 15:42
So isses. Immer, wenn es um maximale Flexibilität geht, sind Interfaces das probate Mittel dazu. Für jedes Fitzchen aber nur noch auf Interfaces zu setzen ist in meinen Augen sinnlos. Wie immer hängt es halt vom Einzelfall ab.
Detlef
  Mit Zitat antworten Zitat
a.def
 
#22
  Alt 12. Apr 2017, 16:32
Zitat:
Wo ist denn nun der Unterschied zwischen TDings und IDings? Ist I komplizierter als T?
Für doof halten darf man mich aber auch nicht.
  Mit Zitat antworten Zitat
EWeiss
 
#23
  Alt 12. Apr 2017, 16:37
Zitat:
Wo ist denn nun der Unterschied zwischen TDings und IDings? Ist I komplizierter als T?
Für doof halten darf man mich aber auch nicht.
Er wollte dir doch nur aufzeigen das es im Grunde genommen Fast das gleiche ist.
Denke nicht das er das böse gemeint hat.

gruss
  Mit Zitat antworten Zitat
a.def
 
#24
  Alt 12. Apr 2017, 16:41
Zitat:
Denke nicht das er das böse gemeint hat.
War es bestimmt auch nicht.

Aber ich finde, dass Interfaces ganz und gar nicht das gleiche sind wie normale Klassen. ich finde normale Klassen wesentlich angenehmer zu schreiben.
Wenn man X Objekte von Y Objekten ableiten muss, macht man im Grundaufbau irgendetwas falsch wie ich finde. Da helfen auch keine Interfaces.
  Mit Zitat antworten Zitat
EWeiss
 
#25
  Alt 12. Apr 2017, 16:48
Kommt halt darauf an für was man es verwendet.

Wenn ich eine Funktion aus meiner DLL öffentlich machen will dann verwende ich Interface weil der
Anwender sich dann nicht mehr um das laden der DLL kümmern muss.
Zudem erspart es mir für eine Funktion mehrere Exports zu generieren die ich so mit einer Erledigen kann.

gruss
  Mit Zitat antworten Zitat
hoika

 
Delphi 10.4 Sydney
 
#26
  Alt 12. Apr 2017, 16:59
Hallo,
wenn ich bestehende Klassen, die nicht von einer Basisklasse abgeleitet sind,
um eine gemeinsame Funktionalität erweitern will,
geht das nur über Interfaces.
Heiko
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

 
Delphi 11 Alexandria
 
#27
  Alt 12. Apr 2017, 17:01
Das Beispiel in #11 von DeddyH zeigt das eigentlich sehr schön.

Ohne Interfaces müsste man Wuppdi so schreiben:

Delphi-Quellcode:
{ TWuppdi }

procedure TWuppdi.Consume(const O: TObject);
begin
  if (O is TEdit) then
   ShowMessage((O as TEdit).DisplayString);
  if (O is TLabel) then
   ShowMessage((O as TLabel).DisplayString);
  if (O is TComboBox) then
   ShowMessage((O as TComboBox).DisplayString);
end;
Man muss also an der Stelle alle Klassen konkret kennen und darauf casten.

Wenn man irgendwann 2 neue Klassen dort anzeigen möchte, muss man diese in der Prozedur nachträglich mit aufnehmen - und in allen anderen Prozeduren, die diese neuen Klassen kennen müssen.

Auf eine gemeinsame Basisklasse zurückzugreifen geht hier ja nicht.

Mit Interfaces ist es halt einfacher, zu prüfen, ob das vorliegende Objekt von der Prozedur bearbeitet werden kann.
Man schaut sich nur noch an, ob das, was ich da habe displayed werden kann oder nicht. Man muss dann nicht mehr wissen, welche Klasse man konkret vorliegen hat und es kann sogar sein, dass man die konkrete Klasse überhaupt nicht kennt.

Wie gesagt, man MUSS das natürlich nicht verwenden, aber es ist gut, wenn man es kennt und darauf zurück greifen kann, wenn man mit einfachen Klassen mal nicht so gut zum Ziel kommt.
  Mit Zitat antworten Zitat
Fritzew

 
Delphi 11 Alexandria
 
#28
  Alt 12. Apr 2017, 17:16
Der grösste Vorteil von Interfaces ist das die Consumer Klasse gar nichts von der Implementation mitbekommt.
Keinerlei Abhängigkeiten zu anderen Klassen. Jeder der versucht Unit Tests zu schreiben und das auch konsequent durchzieht
will die Vorteile nicht mehr missen. Die einzige Abhängigkeit ist die Interface Unit, fertig.

Ich arbeite in der CAD-Branche, wir müssen import und export zu verschiedenen Formaten bereithalten.

Unser Klassen haben einfach Methoden die ein Interface entgegennehmen. Welches Format da dann wirklich dahintersteht is egal.
Die verschiedenen Formate werden einfach bei einer Factory registriert.

function readImport(Import : IImport3d) : boolean; Ob da jetzt ein Step, SAT, Igel oder was auch immer ankommt ist egal.
Die einzelnen Bereiche können unabhängig voneinander getestet werden und gut ist..
Fritz Westermann
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

 
Delphi 12 Athens
 
#29
  Alt 12. Apr 2017, 17:39
Ich verweise da mal schamlos auf meine Reihe von Blog-Artikeln von 2010 zum Visitor Pattern: The Visitor Pattern – Part 1, Part 2, Part 3 und Part 4

Insbesondere Part 2 und 3 gehen auf die Vorteile bei der Verwendung von Interfaces ein. Ist aber auch schon etwas mehr zu lesen...
Uwe Raabe
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

 
Delphi 10.2 Tokyo Starter
 
#30
  Alt 12. Apr 2017, 19:21
Was ich mich schon länger frage: Gibt es eigentlich einen bestimmten Grund, warum Interfaces in Delphi keine privaten Methoden haben dürfen? Unter C++ ist das beispielsweise problemlos möglich.
  Mit Zitat antworten Zitat
Themen-Optionen Tutorial durchsuchen
Tutorial durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:03 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz