AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Algorithmen, Datenstrukturen und Klassendesign [beantwortet] Best pratice: Klassen überspringen beim Vererben?

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

Ein Thema von silver-moon-2000 · begonnen am 14. Aug 2013 · letzter Beitrag vom 14. Aug 2013
Antwort Antwort
silver-moon-2000

Registriert seit: 18. Feb 2007
Ort: Schweinfurt
170 Beiträge
 
Delphi XE Professional
 
#1

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

  Alt 14. Aug 2013, 16:09
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 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?
Tobias
Bitte nicht hauen , ich weiß es nicht besser

Geändert von silver-moon-2000 (14. Aug 2013 um 18:37 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.288 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 16:18
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.
Sebastian Jänicke
Alle eigenen Projekte sind eingestellt, ebenso meine Homepage, Downloadlinks usw. im Forum bleiben aktiv!
  Mit Zitat antworten Zitat
mkinzler
(Moderator)

Registriert seit: 9. Dez 2005
Ort: Heilbronn
39.851 Beiträge
 
Delphi 11 Alexandria
 
#3

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 16:33
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.
Markus Kinzler
  Mit Zitat antworten Zitat
Benutzerbild von Sherlock
Sherlock

Registriert seit: 10. Jan 2006
Ort: Offenbach
3.753 Beiträge
 
Delphi 11 Alexandria
 
#4

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 16:40
Bei Deinem T4 hast Du vergessen d zu implementieren.

Sherlock
Oliver
Geändert von Sherlock (Morgen um 16:78 Uhr) Grund: Weil ich es kann
  Mit Zitat antworten Zitat
mkinzler
(Moderator)

Registriert seit: 9. Dez 2005
Ort: Heilbronn
39.851 Beiträge
 
Delphi 11 Alexandria
 
#5

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 16:41
Bei Deinem T4 hast Du vergessen d zu implementieren.

Sherlock
Nein, ich wollte ja zeigen, das es nicht geht ( im Gegensatz zu Vererbung von einer Klasse)
Markus Kinzler
  Mit Zitat antworten Zitat
Benutzerbild von Sherlock
Sherlock

Registriert seit: 10. Jan 2006
Ort: Offenbach
3.753 Beiträge
 
Delphi 11 Alexandria
 
#6

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 17:11
Achsoooo, alles klar.

Carry on

Sherlock
Oliver
Geändert von Sherlock (Morgen um 16:78 Uhr) Grund: Weil ich es kann
  Mit Zitat antworten Zitat
silver-moon-2000

Registriert seit: 18. Feb 2007
Ort: Schweinfurt
170 Beiträge
 
Delphi XE Professional
 
#7

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 18:36
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,

[...]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 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.
Tobias
Bitte nicht hauen , ich weiß es nicht besser
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
10.934 Beiträge
 
Delphi 12 Athens
 
#8

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 18:41
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.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
silver-moon-2000

Registriert seit: 18. Feb 2007
Ort: Schweinfurt
170 Beiträge
 
Delphi XE Professional
 
#9

AW: Best pratice: Klassen überspringen beim Vererben?

  Alt 14. Aug 2013, 19:11
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.
Tobias
Bitte nicht hauen , ich weiß es nicht besser
  Mit Zitat antworten Zitat
Themen-Optionen Thema durchsuchen
Thema 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 01:51 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