Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   [beantwortet] Best pratice: Klassen überspringen beim Vererben? (https://www.delphipraxis.net/176132-%5Bbeantwortet%5D-best-pratice-klassen-ueberspringen-beim-vererben.html)

silver-moon-2000 14. Aug 2013 15:09


[beantwortet] Best pratice: Klassen überspringen beim Vererben?
 
Hallo zusammen,

erstens ist mir kein besserer Titel eingefallen...
zweitens muss ich warnen, dass ich vermutlich in der nächsten Zeit viele solcher Fragen stellen werde.

Worum es geht:
Ich habe ein Interface (oder eine abstrakte Klasse, kommt auf's gleiche raus). In der Klasse, die das Interface implementiert, soll aber nur ein Teil der Methoden verwendet werden und der Rest in der Ableitung der Implementierung. Wie geht man da am besten vor?

Gegeben sei das folgende Interface
Delphi-Quellcode:
type IMyInterface = interface(IInterface)
  function getName : string;
  function getType : THandlerType;
end;
Die Klasse SaveHandler implementiert das interface, benötigt aber nur die Methode getType. Die Methode getName macht hier noch keinen Sinn, weil es sich bei TSaveHandler um eine teilabstrakte Klasse handelt, die als Grundlage konkreter (TSaveTXT, TSaveCSV) Klassen dient. Erst in den konkreten Klassen muss/kann/darf/soll getName verwendet werden.

Delphi-Quellcode:
type TSaveHandler = class(TInterfacedObject, IMyInterface)
  protected
    procedure SaveFile; virtual; abstract;
  public
    function getType : THandlerType;
end;

type TSaveTXT = class(TSaveHandler)
  protected
    procedure SaveFile; override; //speichert als txt Datei
  public
    function getName : string; //Result := 'save as TXT'
end;

type TSaveCSV = class(TSaveHandler)
  protected
    procedure SaveFile; override; //speichert als csv Datei
  public
    function getName : string; //Result := 'save as CSV'
end;
Für mich ist es hier ein wenig "negativ", dass die Implementierung von getName die Klasse TSaveHandler überspringt.
Wenn ich also eine weitere Klasse TSaveXLS erzeugen wollte, müsste ich in allen "übergeordneten" Klassen/Interfaces nachsehen, was ich implementieren muss, es reicht also nicht, alle Methoden zu implementieren, die in TSaveHandler als
Delphi-Quellcode:
abstract
gekennzeichnet sind.

Wenn ich das vermeiden will, muss ich TSaveHandler "erweitern", so wie es im Folgenden dargestellt ist.


Delphi-Quellcode:
type TSaveHandler = class(TInterfacedObject, IMyInterface)
  protected
    procedure SaveFile; virtual; abstract;
    function getName2 : string; virtual; abstract;
  public
    function getType : THandlerType;
    function getName : string;
end;


procedure TSavehandler.getName : string;
begin
  Result := getName2;
end;
Somit reicht es aus, alle abstrakten Methoden der Basisklasse zu implementieren und man muss nicht in dessen Vorgängern herumsuchen, jedoch handelt man sich dadurch Schreibaufwand ein und umgeht vermutlich auch das DRY-Prinzip?
Wobei ich eben sagen muss, dass ich die zweite Variante übersichtlicher finde, weil eben die Basisklasse "komplett" ist.

Wie seht Ihr das, oder gibt es einen viel besseren Weg? In anderen Worten: Darf/kann man bei der Implementierung von Methoden eine oder mehrere Hierarchie-Ebenen überspringen oder ist das bad-practice?

jaenicke 14. Aug 2013 15:18

AW: Best pratice: Klassen überspringen beim Vererben?
 
Wenn es nicht notwendig ist eine Methode zu überschreiben, dann muss man das auch nicht tun. Dass das in einer weiteren abgeleiteten Klasse doch wieder notwendig sein könnte, ändert daran nichts. Bei Standardklassen von Delphi passiert das auch ständig und muss man selbst ja auch immer wieder mal so machen.

Bei der Implementierung der Ableitung muss man eben schauen was man an Funktionalität anpassen muss und was dafür überschrieben werden muss.

Nebenbei kannst du in der abgeleiteten Klasse auch einfach Strg + Leertaste drücken und siehst dort die Methoden der Vorgängerklassen. Die kannst du dann markieren mit Shift + Pfeil und per Enter die Deklarationen einfügen. Deshalb geht das recht einfach und schnell.

mkinzler 14. Aug 2013 15:33

AW: Best pratice: Klassen überspringen beim Vererben?
 
Zitat:

Ich habe ein Interface (oder eine abstrakte Klasse, kommt auf's gleiche raus).
Nein, weil beim Erben eines Interfaces musst du alles implementieren, bei Erben von einer Klasse nicht.

Delphi-Quellcode:
unit Test;

interface

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

type

  i1 = interface
    procedure c;
    procedure d;
  end;
  T1 = class
    procedure a; virtual; abstract;
    procedure b; virtual; abstract;
  end;
  T2 = class( T1)
   procedure a;
  end;
  T3 = class (T2)
    procedure b;
  end;
  T4 = class (TObject, i1)
    procedure c;
  end;

  TForm2 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure T2.a;
begin
   MessageBox(  0, PChar(Self.ClassName), 'a', 0);
end;

procedure T3.b;
begin
   MessageBox(  0, PChar(Self.ClassName), 'b', 0);
end;

procedure T4.c;
begin
   MessageBox(  0, PChar(Self.ClassName), 'c', 0);
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  o2: T2;
  o3: T3;
begin
  o2 := T2.Create;
  o2.a;
  o3 := T3.Create;
  o3.a;
  o3.b;
end;

end.

Sherlock 14. Aug 2013 15:40

AW: Best pratice: Klassen überspringen beim Vererben?
 
Bei Deinem
Delphi-Quellcode:
T4
hast Du vergessen
Delphi-Quellcode:
d
zu implementieren.

Sherlock

mkinzler 14. Aug 2013 15:41

AW: Best pratice: Klassen überspringen beim Vererben?
 
Zitat:

Zitat von Sherlock (Beitrag 1224784)
Bei Deinem
Delphi-Quellcode:
T4
hast Du vergessen
Delphi-Quellcode:
d
zu implementieren.

Sherlock

Nein, ich wollte ja zeigen, das es nicht geht ( im Gegensatz zu Vererbung von einer Klasse)

Sherlock 14. Aug 2013 16:11

AW: Best pratice: Klassen überspringen beim Vererben?
 
Achsoooo, alles klar.

Carry on

Sherlock

silver-moon-2000 14. Aug 2013 17:36

AW: Best pratice: Klassen überspringen beim Vererben?
 
Zitat:

Zitat von mkinzler (Beitrag 1224782)
Zitat:

Ich habe ein Interface (oder eine abstrakte Klasse, kommt auf's gleiche raus).
Nein, weil beim Erben eines Interfaces musst du alles implementieren, bei Erben von einer Klasse nicht.

[OT] Ach Kinners, ich liebe dieses Forum, da bekomme ich, ganz egal, wie blöd ich frage, immer eine freundliche und hilfreiche Antwort. Eure Geduld ist echt bewundernswert! (KEINE Ironie!)

Häh? Warum kann ich dann mein Beispiel von oben kompilieren?
Stopp, Irrtum, geht doch nicht. Ich war davon überzeugt, dass es ging. Seltsam...
Dann wäre das Problem ja für Interfaces gelöst. Die müssen immer vollständig implementiert werden.

Wenn ich die richtigen Schlussfolgerungen daraus ziehe und auch das, was jaenicke sagt, richtig interpretiere,

Zitat:

Zitat von jaenicke (Beitrag 1224774)
[...]Bei der Implementierung der Ableitung muss man eben schauen was man an Funktionalität anpassen muss und was dafür überschrieben werden muss.[...]

dann bedeutet das, dass ich im Beispiel von oben Folgendermaßen arbeiten muss. Dass ich also das Interface vollständig implementiere, und die Methoden, die ich "noch nicht" brauche, per
Delphi-Quellcode:
abstract
nach oben durchreiche:

Delphi-Quellcode:

type IMyInterface = interface(IInterface)
  function getName : string;
  function getType : THandlerType;
end;

type TSaveHandler = class(TInterfacedObject, IMyInterface)
  protected
    procedure SaveFile; virtual; abstract;
    function getName2 : string; virtual; abstract;
  public
    function getType : THandlerType;
    function getName : string;
end;

type TSaveTXT = class(TSaveHandler)
  protected
    procedure SaveFile; override;
    function getName2 : string; override; //Result := 'txt'
end;


procedure TSavehandler.getName : string;
begin
  Result := getName2;
end;

FOLGEFRAGE DAZU:

Ist es dann nicht sinnvoller, anstatt getName auf das abstrakte getName2 "umzuleiten" (und somit "leeren" Code in TSavehandler zu haben), das Interface aufzutrennen und nur das jeweils nötige zu implementieren?

Delphi-Quellcode:

type ITypeInterface =interface(IInterface)
  getType : THandlerType;
end;

type INameInterface =interface(IInterface)
  getName : string;
end;

type TSaveHandler = class(TInterfacedObject, ITypeInterface)
  protected
    procedure SaveFile(_data : TMyData); virtual; abstract;
  public
    function getType : THandlerType;
end;

type TSaveTXT = class(TSaveHandler, INameInterface)
  protected
    procedure SaveFile(_data : TMyData); override;
    function getName : string; //Result := 'txt'
end;
Oder handle ich mir dabei weitere/andere Probleme ein?

[edit]
Ich versuche mich mal selbst an einer Antwort:
Wenn ich das INameInterface erst auf der Hierarchie-Ebene von TSaveTXT einführe, verbaue ich mir, die ...Handler "generisch" ansprechen zu können. Was ich damit meine:

Delphi-Quellcode:
type TSaveLibrary =class(TObject) //verwaltet alle vorhandenen SaveXXX
  private
    FList : TObjectList<TSaveHandler>;
  public
    procedure SaveDataAs(_qualifier : string; _data : TMyData);
end;

procedure TSaveLibrary.SaveDataAs(_qualifier : string; _data : TMyData);
var i : Integer;
begin
  //wobei sich hier auch evtl. ein TDictionary (vergl. http://www.delphipraxis.net/1224610-post12.html) anbieten würde
  i := 0;
  while ((i < FList.Count) and (FList.Items[i].getName <> _qualifier)) do
    Inc(i);
  FList.Items[i].SaveFile(_data);
end;
Funktioniert natürlich nur dann, wenn TSaveHandler bereits die getName Methode implementiert, und nicht erst TSaveTXT.

Soll heißen: Sobald ich konkrete Klassen über deren gemeinsamen Vorfahren ansprechen muss, muss dieser Vorfahr die Methoden bereits stellen, auch wenn er selbst damit noch nichtsa anfangen kann. *kopfkratz* War irgendwie offensichtlich, oder?

Sorry für meine Fragen, aber Interfaces sind (bis jetzt) für mich Neuland auf das ich mich noch nicht gewagt habe.

[AB HIER UNINTERESSANTE SELBSTGESPRÄCHE - Unterschied Interface - abstrakte Klasse]

Besonders deswegen, weil mir (bei meinem (!) Kenntnisstand) noch nichts untergekommen ist, wo abstrakte Klassen nicht auch ausreichen würden.

Ja, ich weiß, interfaces und abstrakte Klassen sind bereits durchgekaut worden bis zum Erbr*chen und deswegen will ich eigentlich auch keine weitere Diskussion mehr darum führen. Hier jedoch gibt es mMn eine so schöne Zusammenfassung, dass ich diese mal nach meinem Verständnis zusammenfassen will.

Mehrfachvererbung: abstrakte Klassen unterstützen KEINE Mehrfachvererbung, Interfaces schon.

Felder und Logik: Abstrakte Klassen können im Vergleich zu interfaces Felder und bereits zusätzliche Logik ("teilabstrakte Klasse") enthalten.

Eigenschaften/Identität: Interfaces sollen die Eigenschaften der implementierenden Klasse festlegen, während abstrakte Klassen die Identität festlegen. Soll heißen meines Verständnis' nach:
Delphi-Quellcode:
type TAuto = class(TInterfacedObject, IDrivable, IRecycable);
  private
    FMaxSpeed : Integer;
  protected
    procedure FahreVorwärts; virtual; abstract; //aus IDrivable;
    procedure Wiederverwerten; virtual; abstract; //aus IRecycable;
end;

type TPeugeot206 = class(TAuto)
[...]
end;
Die Interfaces IDrivable und IRecycable zeigen an, was TAuto (und alle Nachfahren) machen kann, legen aber nicht genauer fest, auf welche Weise das geschieht.
Es gibt viele Klassen, die mit TAuto nichts gemeinsam haben und dennoch IRecycable implementieren, z.B. TBierflasche, TZahnpastatube

Die abstrakte Klasse TAuto jedoch (als Summe aller Methoden und Felder (?)) legt die Identität aller Nachfahren fest. Ein Nachfahre von TAuto (z.B. TPeugeot206) "ist ein Auto" und nichts anderes mehr, besonders nicht einfach nur "auto-able".

Homogenität/Heterogenität: Wird als Grundlage (z.B. für das Entwickeln von Plugins) eine abstrakte Klasse zur Verfügung gestellt, ist man als Entwickler auf der einen Seite an die Vorgaben (und evtl. in der Klasse bereits existierende Logik) gebunden, was einem auf der einen Seite gewisse Freiheiten nimmt, auf der anderen Seite wird man aber auch an die Hand genommen, da bereits ein Teil der Programmierung für einen erledigt wurde.
Mit anderen Worten: Wenn sich Plugins stark unterscheiden und nur die gleichen Aufrufe teilen, sind Interfaces besser geeignet (Heterogenität). Arbeiten alle Plugins ähnlich, kann mehr Logik in die Basisklasse ausgelagert werden. Hier sind aufgrund der Homogenität abstrakte Klassen besser.

Erweiterbarkeit: Soll später erweitert werden, müssen alle implementierenden Klassen dieses Interfaces geändert werden, während bei einer abstrakten Klasse (eben weil sie Logik enthalten kann) ein default-Verhalten eingebaut werden kann.

Gut, das gibt mir jetzt erst einmal zu denken.

Ich glaube, meine Frage ist für mich hinreichend beantwortet. Danke an alle


Absolutes sorry dafür, dass ich hier so viele Selbstgespräche führe, die für Euch alle nichts Neues bringen.
Aber solange mir etwas nicht absolut Stück für Stück vorgekaut wird, brauche ich recht lange, bis ich es verstehe und der einfachste Weg ist es für mich, alles en détail niederzuschreiben.

Uwe Raabe 14. Aug 2013 17:41

AW: Best pratice: Klassen überspringen beim Vererben?
 
Zitat:

Zitat von silver-moon-2000 (Beitrag 1224768)
Delphi-Quellcode:
type TSaveHandler = class(TInterfacedObject, IMyInterface)
  protected
    procedure SaveFile; virtual; abstract;
    function getName2 : string; virtual; abstract;
  public
    function getType : THandlerType;
    function getName : string;
end;


procedure TSavehandler.getName : string;
begin
  Result := getName2;
end;

Warum deklarierst du nicht gleich getName als virtual-abstract? Der Umweg über getName2 ist doch gar nicht nötig.

silver-moon-2000 14. Aug 2013 18:11

AW: Best pratice: Klassen überspringen beim Vererben?
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1224816)
Warum deklarierst du nicht gleich getName als virtual-abstract? Der Umweg über getName2 ist doch gar nicht nötig.

Da hab' ich gar nicht dran gedacht. Mir war praktisch nicht bewusst, dass ich getName, das ich ja aus dem interface "implementiert" hatte, gleich wieder als abstract definieren kann.


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