PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C++ und "Diamant"-Vererbung


mekakic
2012-07-12, 17:31:00
Momentan verwende ich eine kleine Diamantvererbung, die grob so aussieht:

IConfig
/ \
IBase1 IBase2
\ /
IImpl
|
CImpl

IConfig habe ich mittel virtual Vererbung in IBase1/2 reingeholt, damit ich am Ende in einer Implementierung von IImpl keine zwei Instanzen von IConfig drin habe.

Ist das das nötig oder nur wenn IConfig auch Implementierungen und Member enthält? IBase1/2 sind komplett abstrakte, pure virtual Interfacebeschreibungen. Macht es einen Unterschied, wenn IConfig dies auch wäre?

class IBase1
{
public:
virtual void foo1() = 0;
virtual void foo2() = 0;
virtual void foo3() = 0;
};

class IConfig
{
public:
virtual void init() = 0;
virtual void getConfig() {};
virtual void reload() {};
protected:
std::shared_ptr<XMLConfig> config_ptr;
};

Ich überlege gerade eine "class CConfig : public IConfig" Realisierung von IConfig zu bauen und IConfig pure virtual, komplett abstrakt zu machen, sowie dann den erbenden Klassen diese Implementierung CConfig als Instanz mitzugeben.

Ich würde nämlich gern die (oben verkürzt dargestellte) Implementierung an erbende Klassen verteilen, aber nicht die Eigenschaft verlieren, dass CImpl vom Typ IConfig ist. In der Implementierung von IConfig in CImpl würde man dann alle Function calls als Wrapper auf die Instanz umleiten. Werde ich dadurch das virtual los? Das macht es nämlich immer unübersichtlich wo man virtual vererben muß und wo nicht.

Ich habe dazu was gelesen ... meinte unter dem Thema der Art "realize interface, deploy implementation" aber dazu finde ich aktuell nichts brauchbares. Hat jemand dazu eine Idee? Danke! :)

samm
2012-07-12, 18:33:49
Is-a vs. has-a? Weshalb sollten alle Interfaces und CImpl eine Config sein? Üblicherweise *haben* Objekte eine Config... Wenn dann nur CBase-Objekte eine Konfiguration haben, kannst du dir die virtual-Geschichte ohnehin sparen.

Oder gibt es effektiv einen guten Grund, weshalb die andern Interfaces und die Klasse CImpl eine Config *sein* sollen?

mekakic
2012-07-13, 09:00:39
So in der Art... aber eher "Is-a" vs. "Is-a and Has-a". Die obere Beschreibung ist deutlich vereinfacht, aber man braucht sowohl in IBase1/2 wie in IImpl die Interface Eigenschaften von IConfig. Es existiert auch nur eine Config im Objekt und die wird erst in CImpl implementiert.

Also jede Implementierung von IBase1 oder IBase2 muß konfigurierbar sein. Das interface IImpl ist dann für Implementierungen, die Objekte mit den Eigenschaften von IBase1 und IBase2 sind.

Mit Ausnahme der Eigenschaften, die identisch sind und momentan in IConfig implmentiert sind, aber die überlege ich über eine Instanz zu deployen. Also so in der Art


IConfig <------------CConfig
/ \ |
IBase1 IBase2 /
\ / /
IImpl---------------/
|
CImpl

samm
2012-07-13, 22:08:38
Puh, ähm ja, was sagt man dazu... Damit wird es natürlich noch eine Stufe unpraktischer... Zusammengefasst aus den beiden Posts brauchen die Base-Interfaces und das Impl-Interface die Eigentschaften von IConfig, und nur CImpl hat ein CConfig-Objekt. Damit bleibt dir als halt noch beste Variante, alles bis zu CImpl/CConfig virtual zu halten (Erklärung s.u.).


Aber anhand der geposteten Methodensignaturen von IConfig fällt mir noch etwas ein: Wenn IConfig eh nur extrem generelle Funktionalität darstellt wie "init(), reload() und getConfig()", was IConfig irgendwie in die Nähe von Object rückt ;): was spricht gegen Folgendes, ohne jegliche Implementation einer IConfig:

IBase1 IBase2
\ /
\ /
IImpl
|
CImpl----CConfig

Falls das zu billig ist: um deine ursprünglichen Frage zu beantworten:IConfig habe ich mittel virtual Vererbung in IBase1/2 reingeholt
...
Ist das das nötig oder nur wenn IConfig auch Implementierungen und Member enthält?Mit der Diamant-Vererbung wäre es zwingend nötig bei Implementierungen / Membern in IConfig, denn:
"Konkrete" Methode in IConfig: Auf IConfig::[irgendneKonkreteMethode] könntest du aus CImpl nicht zugreifen, das müsste direkt einen Kompilierfehler geben.

Ohne Implementierung / Member in IConfig wäre es zumindest zu empfehlen, weil CImpl dann direkt ein IConfig ist, wo es nicht um IBaseX- oder IImpl-spezifische Dinge geht, sonst müsstest du immer fully qualified festlegen, aus welchem IBase du eine IConfig-Methode implementierst.


:confused: ok, vielleicht erzähl ich auch Müll - weshalb meldet sich niemand hier, der vor etwas kürzerer Frist mit C++ zu tun hatte? ;)

Gast
2012-07-15, 19:48:34
Warum versuchst du auf biegen und brechen ein schlechtes Design zu erzeugen?

samm
2012-07-15, 21:18:00
Hehe, so wollte ich das ja nicht forumlieren... ;) Ich lasse mich da auch ungern auf Äste hinaus, weil ich auch schon Unschönes fabriziert habe, aber ja, es scheint mir auch um einen "zu vermeiden"-Fall von Design zu gehen. Drum habe ich konkrete Vorschläge gemacht. Hast du auch welche, werter Gast?

Ectoplasma
2012-07-16, 09:27:54
Das Design ist deshalb schlecht, weil IBase1 und IBase2 seymantisch betrachtet keine IConfig sind. OO ist Semantik. Das gleiche üble Design findet sich auch in etlichen Java Beispielen, in denen statische Member gerne, ähnlich wie globale Variablen, in einem Interface deklariert werden, nur damit man sich Schreibarbeit spart.

Gast_samm
2012-07-16, 10:07:57
Auf das Semantik-Problem zielte mein erstes Posting genau ab ;)

mekakic
2012-07-16, 10:25:09
Wie würde man das denn anders gestalten? Viel anders kann ich das auch nicht mehr machen, weil das System schon länger existiere aber im Konkreten Fall ist eines ein VideoDatenStrom und das andere ein MetaDatenStrom und IImpl eben ein VideoMetaDatenstrom ... die drei sind aber alles Interfaces die implementiert werden. Wenn zur Laufzeit neue Abbildungen aufgeschaltet werden müssen, wird auf allen registierten IConfig ein reload() gemacht. Das heißt man braucht diese Eigenschaft erstmal überall drin, was implementiert wird.

Wie kann man das denn anders machen?

Ectoplasma
2012-07-16, 11:35:35
Naja, ich kenne die Anwendung natürlich nicht im Detail, aber so wie es Samm weiter oben dargestellt hat, würde ich es auch zunächst machen. Man bräuchte aber mehr Kontext-Wissen, um ein brauchbares Design hier vorschlagen zu können. Der Ansatz von Samm ist aber schonmal ein guter Anfang.

Sisyphus
2012-07-16, 12:45:42
Fuer viele nichts weiter als eine Provokation, fuer Wissende aber ein Fakt der vielleicht bei einigen eine Ueberlegung anstosst: Vererbung ist in den allermeisten Faellen nichts weiter als voellig unnuetze Abstraktion.

Zu der Zeit die du alleine fuer diese Ueberlegung verplaempert hast musst du dann noch das Raetselraten bei spaeteren Aenderungen oder der kompletten Neuentwicklung wegen der absurden Abhaengigkeiten durch den Abstaktionswahn draufrechnen.

Ectoplasma
2012-07-16, 14:38:30
Naja, ist hier vielleicht nicht ganz das Thema, aber dem Mechanismus der Vererbarkeit, kannst du daraus keinen Vorwurf machen. Er kann durchaus sehr Sinnvoll sein. Leider haben ihn viele nicht richtig verstanden und benutzen ihn auf Teufel komm raus.

del_4901
2012-07-16, 14:46:57
Naja, ist hier vielleicht nicht ganz das Thema, aber dem Mechanismus der Vererbarkeit, kannst du daraus keinen Vorwurf machen. Er kann durchaus sehr Sinnvoll sein. Leider haben ihn viele nicht richtig verstanden und benutzen ihn auf Teufel komm raus.
Naja ist eben das erste was so beigebracht bekommt, es muss also gut sein. :ugly: Ich bin auch mehr ein Freund von Interfacevererbung wo Komplexitaet versteckt wird anstatt sie zu 'erweitern'.

mekakic
2012-07-16, 15:06:59
Naja, ich kenne die Anwendung natürlich nicht im Detail, aber so wie es Samm weiter oben dargestellt hat, würde ich es auch zunächst machen. Man bräuchte aber mehr Kontext-Wissen, um ein brauchbares Design hier vorschlagen zu können. Der Ansatz von Samm ist aber schonmal ein guter Anfang.
Aber das funktioniert nicht, weil die Objekte dann nicht beim System registriert werden können, die brauchen die Eigenschaften von IConfig um einen re-int der verschiedenen Datenströme anzustoßen. CImpl selbst bekommt man als Objekt nicht zu Gesicht, die AbstrakteFabrik für das System liefert einem nur Objekte mit IImpl, IBase1, IBase2 Typen. Eher könnte man IImpl in IBase3 umbauen aber das würde auch ne Menge Arbeit machen, nur um das olle virtual loszuwerden, weil es eigentlich wirklich die Komposition der beiden Typen ist und ohne neuen Code out-of-the-box verarbeitet wird. Prinzipiell funktioniert das ja auch alles ganz gut und ich sehe auch noch nicht wie man die Vererbungssemantik hier für das Problem deutlich vereinfacht bekommt. Ging mir auch eher etwas generell darum, wie man damit umgeht, wenn man solche Konstrukte (Diamant-Vererbung) vorfindet bzw. wie man die virtual Vererbung loswerden könnte.

Naja, ist hier vielleicht nicht ganz das Thema, aber dem Mechanismus der Vererbarkeit, kannst du daraus keinen Vorwurf machen. Er kann durchaus sehr Sinnvoll sein.Es erhöht sich lokal die Kopplung zwischen den in Erbschaft stehenden Komponenten. Dies kann aber eben auch bewusst genutzt werden, um woanders wo man es möchte die Kopplung gerade zu verringern. Das ist dann häufig wesentlich schwieriger oder aufwendiger es anders zu lösen. Wie bei allen Sprachmitteln kommt es immer auf die Situation an.

Gast
2012-07-16, 15:51:37
Naja, ist hier vielleicht nicht ganz das Thema, aber dem Mechanismus der Vererbarkeit, kannst du daraus keinen Vorwurf machen. Er kann durchaus sehr Sinnvoll sein. Leider haben ihn viele nicht richtig verstanden und benutzen ihn auf Teufel komm raus.Mir ist generell nicht ersichtlich wo Vererbung z.B. object composition vorzuziehen ist.

Ectoplasma
2012-07-16, 15:55:18
Ich verstehe schon, das System kommt nicht von dir. Dann wirds natürlich schwierig bezüglich des Designs. Mit loswerden der virtuellen Vererbung meinst du die multiple Vererbung. Das Problem mit dieser ist, dass sie extrem abhängig vom jeweiligen Fall ist. Es gibt dafür kein Patentrezept, wann ich sie benutzen sollte und wann nicht. Grundsätzlich würde ich erst einmal versuche ohne solche Konstrukte auszukommen. Ja sogar ohne Vererbung auszukommen. Das dürfte in deinem Fall schwierig sein.

Also du hast eine Factory, die Objekte vom Type *DatenStrom* erzeugt. Darin sehe ich schonmal keine IConfig. Trotzdem sollen die erzeugten Objekte konfigurierbar sein. Eigentlich bräuchtest du jetzt eine weitere Klasse des Typs "KonfigurierbarerDatenStrom". In diese packst du dein jeweiliges DatenStrom Objekt. "KonfigurierbarerDatenStrom" ist natürlich von IConfig abgeleitet und könnte z.B. als Wrapper für eine Instanz vom Typ IConfig fungieren. Damit könntest du die selbe IConfig Instanz an alle Objekte vom Typ "KonfigurierbarerDatenStrom" verteilen. Damit bist du die meisten Sorgen los und hast auch keine multiplen Vererbungen mehr.

Ectoplasma
2012-07-16, 16:02:52
Mir ist generell nicht ersichtlich wo Vererbung z.B. object composition vorzuziehen ist.

Der Klassiker sind doch GUI Bibliotheken. Ok, wenn du mit einer Sprache wie z.B. Smalltalk arbeitest, braucht man nicht unbedingt Vererbung, da zur Laufzeit geprüft wird, ob ein Objekt eine Nachricht versteht. Wie willst du aber in einem abstrakten Code-Teil in C++, z.B. ein simples "moveWindow" aufrufen, wenn nicht alle Fenster von ein und dem selben Basistypen sind? Ohne Basistyp funktioniert das in C++ nur mit Templates (und den damit verbundenen Nachteilen).

del_4901
2012-07-17, 14:44:32
Der Klassiker sind doch GUI Bibliotheken. Ok, wenn du mit einer Sprache wie z.B. Smalltalk arbeitest, braucht man nicht unbedingt Vererbung, da zur Laufzeit geprüft wird, ob ein Objekt eine Nachricht versteht. Wie willst du aber in einem abstrakten Code-Teil in C++, z.B. ein simples "moveWindow" aufrufen, wenn nicht alle Fenster von ein und dem selben Basistypen sind? Ohne Basistyp funktioniert das in C++ nur mit Templates (und den damit verbundenen Nachteilen).
In dem fall kann man auch auf interfacevererbung zurueckgreifen.
Alternativ kann man die Abstraktion auch von den Fenstern weg zu den Fenstermanagern verschieben, die wissen dann mit welchen Instanzen sie es zu tun haben und casten entsprechend.

Ectoplasma
2012-07-17, 17:58:18
In dem fall kann man auch auf interfacevererbung zurueckgreifen.

Wie soll ich diese Aussage interpretieren? Weiter oben bin ich doch gar nicht auf die Art der Vererbung eingegangen. Letztendlich hat der Gast weiter oben die Frage gestellt, wozu Polymorphie gut sein soll. Meine Aussage war nur, dass man vieles mit Object-Composition lösen kann, aber eben nicht alles. Zumindest nicht auf eine elegante Weise.


Alternativ kann man die Abstraktion auch von den Fenstern weg zu den Fenstermanagern verschieben, die wissen dann mit welchen Instanzen sie es zu tun haben und casten entsprechend.

Der abstrakte Code-Teil muss auch nicht in einer der Basisklassen sein. Ich bevorzuge eh soetwas meist auszulagern.

del_4901
2012-07-17, 22:49:22
Meine Aussage war nur, dass man vieles mit Object-Composition lösen kann, aber eben nicht alles. Zumindest nicht auf eine elegante Weise.
Naja die Fenster koennen auch eine moveable Komponente haben die updatet dann selbststaendig die Position und benachrichtigt alle anderen angeschlossenen Komponenten. Ich finde die Idee gar nicht mal so schlecht.

Ectoplasma
2012-07-18, 09:30:48
Naja die Fenster koennen auch eine moveable Komponente haben die updatet dann selbststaendig die Position und benachrichtigt alle anderen angeschlossenen Komponenten. Ich finde die Idee gar nicht mal so schlecht.

Wie auch immer. In C++ musst du irgendwo, auch für deinen Vorschlag, irgendwo einen polymorphen Ansatz haben. Du musst ja irgendwie an die Moveable-Component herankommen bzw. wird diese in irgendeiner Form an anderer Stelle abstrakt sein müssen. Um solche Dinge wirklich los werden zu wollen, benötigt man andere Sprachen, die zur Laufzeit prüfen, ob eine Nachricht von einem Objekt verstanden wird oder nicht.