![]() |
[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:
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.
type IMyInterface = interface(IInterface)
function getName : string; function getType : THandlerType; end;
Delphi-Quellcode:
Für mich ist es hier ein wenig "negativ", dass die Implementierung von getName die Klasse TSaveHandler überspringt.
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; 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:
gekennzeichnet sind.
abstract
Wenn ich das vermeiden will, muss ich TSaveHandler "erweitern", so wie es im Folgenden dargestellt ist.
Delphi-Quellcode:
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?
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; 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? |
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. |
AW: Best pratice: Klassen überspringen beim Vererben?
Zitat:
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. |
AW: Best pratice: Klassen überspringen beim Vererben?
Bei Deinem
Delphi-Quellcode:
hast Du vergessen
T4
Delphi-Quellcode:
zu implementieren.
d
Sherlock |
AW: Best pratice: Klassen überspringen beim Vererben?
Zitat:
|
AW: Best pratice: Klassen überspringen beim Vererben?
Achsoooo, alles klar.
Carry on Sherlock |
AW: Best pratice: Klassen überspringen beim Vererben?
Zitat:
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:
Delphi-Quellcode:
nach oben durchreiche:
abstract
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:
Oder handle ich mir dabei weitere/andere Probleme ein?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; [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:
Funktioniert natürlich nur dann, wenn TSaveHandler bereits die getName Methode implementiert, und nicht erst TSaveTXT.
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; 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. ![]() 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:
Die Interfaces IDrivable und IRecycable zeigen an, was TAuto (und alle Nachfahren) machen kann, legen aber nicht genauer fest, auf welche Weise das geschieht.
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; 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. |
AW: Best pratice: Klassen überspringen beim Vererben?
Zitat:
|
AW: Best pratice: Klassen überspringen beim Vererben?
Zitat:
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:48 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz