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 4 von 5   « Erste     234 5   
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, 106x aufgerufen)

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

 
Delphi 10.4 Sydney
 
#31
  Alt 12. Apr 2017, 19:25
Hm?
wieso privat?
Die Methoden müssen doch implementiert werden,
wenn sie privat wären, macht das doch keinen Sinn, oder?
Heiko
  Mit Zitat antworten Zitat
Fritzew

 
Delphi 11 Alexandria
 
#32
  Alt 12. Apr 2017, 19:37
Zitat:
wieso privat?
Die Methoden müssen doch implementiert werden,
wenn sie privat wären, macht das doch keinen Sinn, oder?
Interface Methoden sind immer aufrufbar, egal wo sie definiert sind.
Ich mache das immer so, dann lassen Sich die Funktionen nur über das Interface ansprechen, verhindert einfach dass man aus versehen Klassen-Instanzen und Interfaces mixed.
Als IInterface geht es als Tclass nicht
Fritz Westermann
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

 
Delphi 10.2 Tokyo Starter
 
#33
  Alt 12. Apr 2017, 20:23
wieso privat?
Die Methoden müssen doch implementiert werden,
wenn sie privat wären, macht das doch keinen Sinn, oder?
Naja protected meinetwegen, um bei Klassen-Terminologie zu bleiben.

Interface Methoden sind immer aufrufbar, egal wo sie definiert sind.
Ich mache das immer so, dann lassen Sich die Funktionen nur über das Interface ansprechen, verhindert einfach dass man aus versehen Klassen-Instanzen und Interfaces mixed.
Als IInterface geht es als Tclass nicht
Hintergrund meiner Frage ist, dass ich Interfaces sehr gerne zum Vermeiden von zirkulären Unit-Referenzen bzw. "Super-Units" benutzen würde. Habe ab und zu mehrere Klassen, die sich "kennen" müssen und untereinander auf interne Methoden zugreifen. Öffentlich will ich diese Methoden nicht deklarieren, weil ein Zugriff von außen unerwünscht ist.
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

 
Delphi 10.1 Berlin Enterprise
 
#34
  Alt 12. Apr 2017, 21:53
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.
Weil C++ keine Interfaces hat, sondern das alles (abstrakte) Klassen sind? Und Klassen haben nunmal Sichtbarkeiten.
Stefan
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

 
Delphi 10.2 Tokyo Starter
 
#35
  Alt 12. Apr 2017, 22:03
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.
Weil C++ keine Interfaces hat, sondern das alles (abstrakte) Klassen sind? Und Klassen haben nunmal Sichtbarkeiten.
Das weiß ich, aber dank multiple-inheritance ist es ja dennoch 1 zu 1 das Gleiche. Mit dem C++ Vergleich wollte ich aber auch nur ausdrücken, dass es technisch auf jeden Fall möglich ist und ich mich deshalb wundere, dass Delphi hier eine Einschränkung vorgibt.
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

 
Delphi 12 Athens
 
#36
  Alt 12. Apr 2017, 22:22
Hintergrund meiner Frage ist, dass ich Interfaces sehr gerne zum Vermeiden von zirkulären Unit-Referenzen bzw. "Super-Units" benutzen würde. Habe ab und zu mehrere Klassen, die sich "kennen" müssen und untereinander auf interne Methoden zugreifen. Öffentlich will ich diese Methoden nicht deklarieren, weil ein Zugriff von außen unerwünscht ist.
Dann deklariere die Interfaces in zwei Units: eine mit den öffentlichen und eine mit den protected. Streng genommen sind protected Methoden bei Klassen auch keine saubere Lösung für eine solche Trennung (was ja durch ein strict protected auch forciert werden kann). Insofern ist das eh nur eine Konvention und die kannst du auf uses-Ebene ebenso gut (oder schlecht) durchsetzen.
Uwe Raabe
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

 
Delphi 10.2 Tokyo Starter
 
#37
  Alt 12. Apr 2017, 23:26
Hintergrund meiner Frage ist, dass ich Interfaces sehr gerne zum Vermeiden von zirkulären Unit-Referenzen bzw. "Super-Units" benutzen würde. Habe ab und zu mehrere Klassen, die sich "kennen" müssen und untereinander auf interne Methoden zugreifen. Öffentlich will ich diese Methoden nicht deklarieren, weil ein Zugriff von außen unerwünscht ist.
Dann deklariere die Interfaces in zwei Units: eine mit den öffentlichen und eine mit den protected. Streng genommen sind protected Methoden bei Klassen auch keine saubere Lösung für eine solche Trennung (was ja durch ein strict protected auch forciert werden kann). Insofern ist das eh nur eine Konvention und die kannst du auf uses-Ebene ebenso gut (oder schlecht) durchsetzen.
Ja, vielleicht bin ich da einfach zu penibel. Ich gehöre auch tatsächlich zu den ca. 10 Leuten weltweit, die konsequent strict private verwenden.
  Mit Zitat antworten Zitat
Benutzerbild von Stevie
Stevie

 
Delphi 10.1 Berlin Enterprise
 
#38
  Alt 13. Apr 2017, 01:04
Ja, vielleicht bin ich da einfach zu penibel. Ich gehöre auch tatsächlich zu den ca. 10 Leuten weltweit, die konsequent strict private verwenden.
Wenn die Class completion da nich immer drauf schei*** würde, wär ich der elfte.
Stefan
  Mit Zitat antworten Zitat
delphirocks
 
#39
  Alt 14. Apr 2017, 22:15
Interfaces haben in C++ normalerweise auch keine privaten Member, sonder sind als public pure abstrakte Methoden implementiert.

Ich denke da gibt es keinen grossen Unterschied zu Delphi:
Code:
struct IWriter {  // structs immer public
   virtual void Write() = 0;
};

class FileWriter : public IWriter {
public:
   void Write() override { doTheWriting(); }
};
  Mit Zitat antworten Zitat
hph97
 
#40
  Alt 10. Okt 2017, 10:05
Warum funktioniert das Beispiel von DeddyH nur, wenn alles in der selben Unit implementiert ist? Wenn ich diesen kompletten Interface Teil in einer Unit2 auslagere so bekomme ich die Compiler Fehlermeldung "E2010 Inkompatible Typen IDisplay und TEdit".
  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 03:04 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