AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Tutorial Interfaces
Tutorial durchsuchen
Ansicht
Themen-Optionen

Tutorial Interfaces

Ein Tutorial von Fritzew · begonnen am 12. Apr 2017 · letzter Beitrag vom 11. Okt 2017
Antwort Antwort
Seite 2 von 5     12 34     Letzte »    
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, 113x aufgerufen)

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

 
Delphi 12 Athens
 
#11
  Alt 12. Apr 2017, 15:02
Exakt, ich habe da mal schnell ein Beispiel gebaut (ich habe die Standard-Benennung einfach beibehalten, da die Komponenten eh keinen tieferen Sinn haben):
Delphi-Quellcode:
unit Unit6;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  IDisplay = interface
    ['{B515DBE1-9D0E-4491-BEE8-85805852870B}']
    function DisplayString: string;
  end;

  TEdit = class(VCL.StdCtrls.TEdit, IDisplay)
  public
    function DisplayString: string;
  end;

  TLabel = class(VCL.StdCtrls.TLabel, IDisplay)
  public
    function DisplayString: string;
  end;

  TComboBox = class(VCL.StdCtrls.TComboBox, IDisplay)
  public
    function DisplayString: string;
  end;

  TWuppdi = class
    procedure Consume(const Display: IDisplay);
  end;

  TForm6 = class(TForm)
    Edit1: TEdit;
    ComboBox1: TComboBox;
    Label1: TLabel;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private-Deklarationen }
    FWuppdi: TWuppdi;
  public
    { Public-Deklarationen }
  end;

var
  Form6: TForm6;

implementation

{$R *.dfm}

{ TEdit }

function TEdit.DisplayString: string;
begin
  Result := Text;
end;

{ TLabel }

function TLabel.DisplayString: string;
begin
  Result := Caption;
end;

{ TComboBox }

function TComboBox.DisplayString: string;
begin
  if ItemIndex > -1 then
    Result := Items[ItemIndex]
  else
    Result := '';
end;

{ TWuppdi }

procedure TWuppdi.Consume(const Display: IDisplay);
begin
  ShowMessage(Display.DisplayString);
end;

procedure TForm6.Button1Click(Sender: TObject);
begin
  FWuppdi.Consume(Edit1);
end;

procedure TForm6.Button2Click(Sender: TObject);
begin
  FWuppdi.Consume(ComboBox1);
end;

procedure TForm6.Button3Click(Sender: TObject);
begin
  FWuppdi.Consume(Label1);
end;

procedure TForm6.FormCreate(Sender: TObject);
begin
  FWuppdi := TWuppdi.Create;
end;

procedure TForm6.FormDestroy(Sender: TObject);
begin
  FWuppdi.Free;
end;

end.
Detlef
  Mit Zitat antworten Zitat
Bbommel

 
Delphi 12 Athens
 
#12
  Alt 12. Apr 2017, 15:05
Hm, du meinst, dass du sowas machen könntest?
Delphi-Quellcode:
  TRumsButton = class(TButton,ISuperIntf)
  public
    procedure Blubb;
  end;

[...]
var
  customer: TCustomer;
  rums: TRumsButton;
begin
[...]
  customer.wuppdi(rums);
[...]
end;
Wäre das von der Synatx her richtig und das, was du meinst? Okay, zugegeben, das ginge mit abtrakten Klassen nicht.

PS: Edith hat sich gemeldet, aber ich poste es dennoch mal, um bei den vorherigen, ganz einfachen Beispielen zu bleiben.

Geändert von Bbommel (12. Apr 2017 um 15:07 Uhr) Grund: Überflüssiges override raus - copy&paste-Fehler :)
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

 
Delphi 12 Athens
 
#13
  Alt 12. Apr 2017, 15:11
Und was meinst du hier mit "Hierarchie einhalten"? Ich muss von der abstrakten Basisklasse ableiten - genauso wie du das interface implementieren musst. Das gibt sich doch eigentlich nichts.
Das Interface kann ich aber in verschiedenen Klassen implementieren, die eine beliebige Vererbungs-Hierarchie haben können und im Extremfall lediglich TObject als gemeinsamen Vorfahren haben.

Ich implementiere schon mal gern Interfaces in Forms, Frames oder Datenmodulen (z.B. ILogger-Implementierungen, die Log-Meldungen in Memos oder Datenbanken schreiben). Das wäre mit abstrakten Klassen gar nicht möglich.

Das Problem mit solchen Tutorials ist halt immer, daß es einfach gehalten sein soll, damit man das dahinter stehende Prinzip versteht. In der Regel gibt es bei solch einfachen Fällen dann natürlich auch valide alternative Lösungen. Ein Tutorial ist eben kein Beispiel für eine Killer-Anwendung.
Uwe Raabe
  Mit Zitat antworten Zitat
a.def
 
#14
  Alt 12. Apr 2017, 15:12
Zitat:
(z.B. ILogger-Implementierungen, die Log-Meldungen in Memos oder Datenbanken schreiben).
Und für so eine einfache Sache braucht man ein kompliziertes Interface?
  Mit Zitat antworten Zitat
Benutzerbild von DeddyH
DeddyH

 
Delphi 12 Athens
 
#15
  Alt 12. Apr 2017, 15:19
Was soll an einem ILogger-Interface kompliziert sein? Da genügt im einfachsten Fall eine Log-Methode mit einem String-Parameter.
Detlef
  Mit Zitat antworten Zitat
Bbommel

 
Delphi 12 Athens
 
#16
  Alt 12. Apr 2017, 15:22
Das Problem mit solchen Tutorials ist halt immer, daß es einfach gehalten sein soll, damit man das dahinter stehende Prinzip versteht. In der Regel gibt es bei solch einfachen Fällen dann natürlich auch valide alternative Lösungen. Ein Tutorial ist eben kein Beispiel für eine Killer-Anwendung.
Joa, daran wird es wohl liegen. Die meisten Sachen, die ich dazu gelesen habe, bauten die Beispiele tatsächlich immer recht einfach auf - ist ja auch klar, um die Syntax und das Grundprinzip zu lernen -, waren dadurch aber eigentlich auch immer mit den abstrakten Klassen "kompatibel".

Insofern hat mich die Diskussion, die sich jetzt hier daraus ergeben hat, tatsächlich weitergebracht. Mal sacken lassen und mal schauen, wann ich demnächst auf ein Problem stoße, wo ich das dann auch mal anwenden kann. Danke euch allen für die Anregungen!
  Mit Zitat antworten Zitat
a.def
 
#17
  Alt 12. Apr 2017, 15:24
Das geht doch auch viel einfacher ohne ein Interface. Eine einfache Klasse reicht.
Interfaces sind einfach nur komplizierter Spökes den man erst wieder umständlich erlernen muss Ich selber komme mit normalen Klassen wunderbar klar und habe noch keine Funktionalität vermisst.
  Mit Zitat antworten Zitat
Benutzerbild von DeddyH
DeddyH

 
Delphi 12 Athens
 
#18
  Alt 12. Apr 2017, 15:27
Wo ist denn nun der Unterschied zwischen TDings und IDings? Ist I komplizierter als T?
Detlef
  Mit Zitat antworten Zitat
EWeiss
 
#19
  Alt 12. Apr 2017, 15:30
Wo ist denn nun der Unterschied zwischen TDings und IDings? Ist I komplizierter als T?
Nein aber das eine Dings hat ein T und das andere Dings ein I von daher doch etwas kompliziert. LOL
Aber davon ab Interface in DLL's sind eine feine Sache.

Ausgenomen davon wenn da nicht so viele Getter und Setter wären die im Interface mit übernommen werden müssen.

gruss

Geändert von EWeiss (12. Apr 2017 um 15:32 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

 
Delphi 11 Alexandria
 
#20
  Alt 12. Apr 2017, 15:33
Eigentlich hatte ich meinen Beitrag vorhin verworfen, aber vielleicht hilft er ja doch noch wem weiter ->

Vielleicht darf ich gleich nochmal auf mein Tutorial verlinken: http://www.delphipraxis.net/183702-i...-factorys.html

Neben der schon erwähnten möglichen Referenzzählung (wenn man es braucht und will) arbeitet man bei Interfaces eher mit Funktionalitäten statt mit konkreten Klassen.

Jedes Objekt kann mehrere Interfaces unterstützen, was bei Verwendung von Basisklassen in Delphi nicht geht.

Ich kann also alle Objekte bewegen, die IMove unterstützen und alle Objekte Loggen, die ILogging unterstützen.
Die konkrete Klasse spielt in dem Moment keine Rolle mehr. Jede im Projekt verwendete Klasse kann kein, eins oder beide Interfaces unterstützen.

Wenn man ohne Interfaces auskommt spricht da überhaupt nichts dagegen.
Der Einsatz von Interfaces erzeugt auch durchaus einigen Mehraufwand und Umstellung beim Umgang mit Objekten.

Bei komplexen Projekten kann sich der aber lohnen, da man u.U. mehr Struktur in das Projekt bekommen und ggf. Klassen später auch leichter austauschen kann. Man ist dann halt nicht von einer bestimmten Basisklasse abhängig.
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 2 von 5     12 34     Letzte »    

 

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 11:30 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