Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   vererbende Klasse oder Interface? oder beides? (https://www.delphipraxis.net/177208-vererbende-klasse-oder-interface-oder-beides.html)

Vis 24. Okt 2013 14:30

vererbende Klasse oder Interface? oder beides?
 
Hallo. Ich bin gerade am kämpfen/lernen mit Vererbung und Interfaces und bräuchte da mal eine kompetente Meinung.

Meine Problemstellung:
Ich habe verschiedene Messgeräte die ich über GPIB anspreche. Das Schreiben/Lesen ist bei den Messgeräten unterschiedlich. Deshalb wollte ich ein Interface ISchreibenLesen verwenden, dessen Methoden dann jede Klasse implementieren muss. Theoretisch könnte ich ja aber auch abstrakte Methoden in der Oberklasse deklarieren und diese dann jeweils in der Unterklasse überschreiben. Kann man da sagen was besser ist?
Die Oberklasse hatte ich mir gedacht, da ja alle Messgeräte gewisse gemeinsame Felder besitzen (Adresse usw.)

TMessgeräte = class ((TInterfacedObject))

TMessgerätA = class (TMessgeräte, ISchreibenLesen) TMessgerätB = class (TMessgeräte, ISchreibenLesen)


Und noch eine Frage. Die Messgeräte sollen eine Property Range bekommen (mit Getter und Setter).
In der Oberklasse deklariert, wird die Property an die Unterklassen vererbt, die Getter/Setter müssten dann abstract sein. Oder anders, die Property im Interface deklarieren (hier auch Getter/Setter). Dann muss ich in der Unterklasse aber wieder eine Property deklarieren, das wird irgendwie doch nicht mehr so übersichtlich.

Wie würdet ihr sowas machen? Wie gesagt, bin da noch stark am Anfang mit Interfaces und Vererbung und für jede Anregung dankbar.

Grüße

Uwe Raabe 24. Okt 2013 15:04

AW: vererbende Klasse oder Interface? oder beides?
 
Ob besser oder schlechter kann man hier nicht sagen. Das kommt wesentlich auf den Rest der Anwendung an. Interfaces bieten Referenzzählung, blähen den Code aber auch etwas auf. Wenn du alles auf einen gemeinsamen Vorfahren zurückführen kannst, ist eine abstrakte Klasse auch keine schlechte Wahl.

Die Getter und Setter kannst du problemlos als abstract deklarieren. Die abgeleitete Klasse braucht dann auch nicht das Property nochmal zu deklarieren - es genügt, wenn die Getter und Setter überschrieben werden.

romankassebaum 24. Okt 2013 20:44

AW: vererbende Klasse oder Interface? oder beides?
 
Hallo,

Interfaces bieten zwei grundlegende Vorteile:

1. Durch die Referenzzählung müssen Sie nicht explizit freigegeben werden.
2. Ein Interface kann von verschiedenen Objekten implementiert werden, die gerade nicht über einen gemeinsamen Vorfahren verfügen.

Da Du in Deinem Beispiel den gemeinsamen Vorfahren TMessgerät hast, denke ich, dass Du keine Interfaces benötigst.

In Deinem Beispiel kannst Du übrigens bereits das TMessgerät mit dem Interface ISchreibenLesen verbinden, da die Erben es ohnehin bereitstellen sollen.

--
Viele Grüße,

Roman Kassebaum
Embarcadero Technology Partner
Embarcadero MVP
Blog: http://blog.kassebaum.eu

Vis 25. Okt 2013 07:49

AW: vererbende Klasse oder Interface? oder beides?
 
Ok, Danke. Das war schon mal sehr interessant.

Wenn ich das Interface ISchreibenLesen mit der Oberklasse TMessgeräte verbinde, dann müsste ich doch eigentlich die Methoden auch in TMessgeräte implementieren. Das möchte ich ja aber erst in den abgeleiteten Klassen. Also müsste ich ja folglich die vom Interface bereitgestellten Methoden als abstract deklarieren. In meinem Fall ist das vielleicht zuviel des Guten und ich deklariere die Methoden gleich in TMessgeräte ohne das Interface. Ansonsten wäre ja 3-mal eine Methode deklariert (im Interface, Oberklasse, Unterklasse) + die Implementierung. Das kann schon auch unübersichtlich werden.

Werd mich mal ran machen das umzusetzen!

Der schöne Günther 25. Okt 2013 10:27

AW: vererbende Klasse oder Interface? oder beides?
 
Genau das gefällt mir auch nicht wirklich. Die Schuld liegt da irgendwie eher an der Delphi-Sprache selber, "echte" abstrakte Klassen kennt es nicht.

Delphi-Quellcode:
   INeustartbar = interface
   ['{C952DFE2-65A3-48A4-8681-81F97946DB5A}']
      procedure restart();
   end;

   ISchreibenLesen = interface
   ['{DBAF7078-C915-471F-8C37-F679253220BE}']
      procedure writeStuff();
      procedure readStuff();
   end;
                                                                  (
   AMessgerät = class abstract(TInterfacedObject, ISchreibenLesen)
      public // Warum ist das hier überhaupt notwendig?
         procedure readStuff; virtual; abstract;
         procedure writeStuff; virtual; abstract;
   end;

   TMessgerät1 = class(AMessgerät)
      procedure spezialKram();
   end;

   TMessgerät2 = class(AMessgerät, INeustartbar)
      public
         procedure spezialKram();
         procedure restart();
   end;

romankassebaum 25. Okt 2013 22:27

AW: vererbende Klasse oder Interface? oder beides?
 
Ich hatte ja gestern schon gesagt, dass man Interfaces sehr gut nutzen kann, um Objekte, die keinen gemeinsamen Vorfahren habe, anzusprechen.
Für die zweite Aufgabe, die Referenzzählung, kann man übrigens auch einen ObjGuard (http://blog.kassebaum.eu/?p=87) nehmen.

Was mir an der vorgestellten Lösung überhaupt nicht gefällt, ist, dass man von TInterfacedObject ableiten muss. Dadurch "weiß" TMessgerät bereits, dass die abgeleiteten Klassen Interfaces nutzen werden.

Ich würde also die Interfaces weglassen.

--
Roman Kassebaum
Embarcadero Technology Partner
Embarcadero MVP
Blog: http://blog.kassebaum.eu

jaenicke 26. Okt 2013 08:33

AW: vererbende Klasse oder Interface? oder beides?
 
Delphi-Quellcode:
   INeustartbar = interface
   ['{C952DFE2-65A3-48A4-8681-81F97946DB5A}']
     procedure restart();
   end;

   ISchreibenLesen = interface
   ['{DBAF7078-C915-471F-8C37-F679253220BE}']
     procedure writeStuff();
     procedure readStuff();
   end;
                                                                 
   TMessgerät = class abstract(TInterfacedObject)
   end;

   TMessgerät1 = class(TMessgerät, ISchreibenLesen)
     procedure readStuff;
     procedure writeStuff;
     procedure spezialKram();
   end;

   TMessgerät2 = class(TMessgerät, ISchreibenLesen, INeustartbar)
   public
     procedure readStuff;
     procedure writeStuff;
     procedure spezialKram();
     procedure restart();
   end;
Das geht genauso. Das Interface muss man ja nicht in der Oberklasse angeben.
Delphi-Quellcode:
var
  a: TMessgerät;
  b: ISchreibenLesen;
begin
  a := TMessgerät2.Create;
  b := a as ISchreibenLesen;
  b.readStuff;
end;
(Wobei das Objekt nach der Methode durch das Interface freigegeben wird.)

// EDIT:
Zitat:

Zitat von romankassebaum (Beitrag 1233232)
Was mir an der vorgestellten Lösung überhaupt nicht gefällt, ist, dass man von TInterfacedObject ableiten muss. Dadurch "weiß" TMessgerät bereits, dass die abgeleiteten Klassen Interfaces nutzen werden.

Warum muss man? Man kann die notwendigen Methoden (QueryInterface, ...) auch in den Unterklassen implementieren. Ich halte das nicht für sinnvoll, kann man aber. Man verliert dann aber die Möglichkeit von der Oberklasse auf das Interface zu casten.

romankassebaum 26. Okt 2013 08:53

AW: vererbende Klasse oder Interface? oder beides?
 
Es ist ganz wichtig, dass Du die Objektinstanz nicht in einer Variablen merkst. Sobald die Referenzzählung das Objekt freigibt, wäre die Instanz nicht mehr gültig.

Delphi-Quellcode:
var
  b: ISchreibenLesen;
begin
  b := TMessgerät2.Create;
  b.readStuff;
end;
--
Roman Kassebaum
Embarcadero Technology Partner
Embarcadero MVP
Blog: http://blog.kassebaum.eu

jaenicke 26. Okt 2013 09:55

AW: vererbende Klasse oder Interface? oder beides?
 
Außer zur Initialisierung in einer lokalen Variable, da finde ich das in Ordnung. Aber sobald das Objekt das erste Mal in einer Interfacereferenz gelandet ist, darf man die Objektvariable natürlich nicht mehr nutzen.

Deshalb ist es auch besser entweder Interfaces oder Objekte zu nutzen und das durch die gesamte Vererbung durch.

Oder man deaktiviert die Referenzzählung.

sx2008 26. Okt 2013 12:44

AW: vererbende Klasse oder Interface? oder beides?
 
Ich würde Datentransport und Datenaufbereitung strikt voreinander trennen.
Das bedeutet dass es eine Klasse für GPIB gibt die rein nur für den Datentransport zuständig ist
und eine weitere (Basis-)Klasse TMessGeraet die für die Aufbereitung der Daten verantwortlich ist.

Dabei benützt die Klasse TMessGeraet ihren Kollegen TGPIB. (Assoziation).

Es liegt also weder eine Vererbung vor noch benötigt man ein Interface.
Man könnte sogar eine weitere Klasse für das Datenprotokoll einsetzen falls ein einheitliches Protokoll wie CS-80 (Command Set 80) benützt wird.

Man kann diesen Ansatz mit folgender Kette vergleichen:
Code:
TDatabase <- TQuery <- TDBEdit
TDatabase sorgt für den Datentransport
TQuery entspricht praktisch dem Protokoll auf Datenbankebene und
TDBEdit repräsentiert die Daten auf Anwendungsebene.

Jede Klasse dient nur einem bestimmten Zweck wobei die Klassen miteinander kollaborieren und in einer "benützt"-Beziehung stehen.
Wichtig ist noch die Pfeilrichtung der Abhängigkeiten zu beachten.
Die Klasse TGPIB darf die Klasse TMessGeraet nicht kennen; umgekehrt kennt und benützt aber TMessgeraet die Klasse TGPIB.

Vis 28. Okt 2013 09:44

AW: vererbende Klasse oder Interface? oder beides?
 
Zitat:

Zitat von jaenicke (Beitrag 1233246)
[DELPHI]
Delphi-Quellcode:
var
  a: TMessgerät;
  b: ISchreibenLesen;
begin
  a := TMessgerät2.Create;
  b := a as ISchreibenLesen;
  b.readStuff;
end;
(Wobei das Objekt nach der Methode durch das Interface freigegeben wird.)

Ein solcher Aufruf ist mir noch nicht geläufig. Wo liegt der Unterschied wenn ich stattdessen mit dem Objekt a arbeite? Also a.readStuff.


@sx2008
Also der Datentransport ist schon in ein eigenen Klasse mit direktem Zugriff auf den GPIB-Bus.
Delphi-Quellcode:
procedure send (addr : integer; s : AnsiString; var status : integer);
procedure enter (var s : Ansistring; maxlen : word; var len : word;
                       addr : integer; var status : integer);

procedure ieee488_send (addr : longint; c : Pointer; len : longint; var status : longint) stdcall;
        external 'IEEE_32M.DLL' name 'IEEE488_SEND';
procedure ieee488_enter (c : Pointer; maxlen : longint; var len : longint;
                 addr : longint; var status : longint) stdcall;
Also diese Klasse TGPIB weiß auch nichts von anderen Klassen.

Nur sind eben die Befehle für die Messgeräte unterschiedlich, z.B. Agilent und Keithley.
Delphi-Quellcode:
procedure send (22, 'ID?', status); //Agilent
procedure send (22, 'IDN?', status); //Keithley
Für jede Unterklasse muss man also fast alle Methoden implementieren. Den Vorteil den ich sehe, ist dass man einmal Methodennamen festlegt (auf Höhe der abstrakten TMessgeräte-Klasse), die dann immer wieder gebraucht werden und man so eine einheitliche Syntax über alle Messgeräte hat.

Vergleichen kann man das vielleicht mit der IVIDmm Klasse zur Ansteuerung von Messgeräten. Die besitzt auch ganz allgemeine Methoden Initialize, Close, Configure und Propertys Measurement.Read usw. Was am Ende für ein Messgerät dran hängt ist egal.

Ob es sich bei mir wirklich so allgemeingültig durchführen lässt, bin ich gerade am probieren.

sx2008 28. Okt 2013 10:05

AW: vererbende Klasse oder Interface? oder beides?
 
Zitat:

Zitat von Vis (Beitrag 1233382)
Nur sind eben die Befehle für die Messgeräte unterschiedlich, z.B. Agilent und Keithley.
Delphi-Quellcode:
procedure send (22, 'ID?', status); //Agilent
procedure send (22, 'IDN?', status); //Keithley
Für jede Unterklasse muss man also fast alle Methoden implementieren.

Muss man nicht unbedingt wenn der Unterschied nur in der Schreibweise der Befehle liegt.

In der Basisklasse führst du eine virtuelle Funktion ein die eine Kennung in einen Befehl umsetzt:
Delphi-Quellcode:
const
  QUERYSTATUS = 1;
  RESETDEVICE = 2;
  // usw.

function TMessgeraet.GetCommandString(commandid:Integer):string; // virtual, protected
begin
  raise Exception.CreateFmt('command %d not implemented (%s)', [commandid, Classname]);
end;

function TMessgeraetAgilent.GetCommandString(commandid:Integer):string; // override;
begin
  case commandid of
    QUERYSTATUS: Result := 'ID?';
    RESETDEVICE: Result := ...;
  else
    result := inherited GetCommandString(commandid);
end;
Anstelle der Konstanten kann man auch einen Aufzählungstyp verwenden.

jaenicke 28. Okt 2013 10:07

AW: vererbende Klasse oder Interface? oder beides?
 
Zitat:

Zitat von Vis (Beitrag 1233382)
Ein solcher Aufruf ist mir noch nicht geläufig. Wo liegt der Unterschied wenn ich stattdessen mit dem Objekt a arbeite? Also a.readStuff.

Man braucht für die Initialisierung kein weiteres Interface und muss diese Funktionalität auch nicht nach außen veröffentlichen. Danach packt man das Objekt in eine Interfacevariable und aktiviert damit die Referenzzählung.

Dieses Interface kann dann auf die zur Initialisierung notwendigen Routinen nicht zugreifen und es gibt auch kein anderes Interface, auf das man dafür casten könnte.

Theoretisch kann man auch hinterher noch auf den Objekttyp casten, sofern man sich im gleichen Modul der Anwendung befindet (selbe Exe, selbe DLL, ..), aber das sollte bei sauberer Implementierung aller Schnittstellen nicht notwendig sein.

Vis 28. Okt 2013 16:44

AW: vererbende Klasse oder Interface? oder beides?
 
@sx2008
interessante Idee. Werd ich mal im Hinterkopf behalten.

@jaenicke
danke für die Antwort. Vielleicht werd ichs demnächst verwenden.


Alle Zeitangaben in WEZ +1. Es ist jetzt 04:16 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