Braucht man Mehrfachvererbung
-
Shade Of Mine schrieb:
Kapiere ich nicht.
Warum ist es besser von IFooable zu erben und den Code von AbstractFoo::doFoo() zu copy&pasten als direkt von AbstractFoo zu erben? Welchen Vorteil bringt mir das?Du musst keinen Code Copy-Pasten. Ich erklärs nochmal: Du definierst ein Interface IFooable und schreibst die Standard-Implementierung DefaultFooable implements IFooable. Nun möchtest Du Fooables schreiben, die diese Standardimplementierung nutzen. Nun leitest Du aber nicht direkt mit extends von DefaultFooable ab sondern nutzt DefaultFooable in diesen Klassen per Komposition. Zusätzlich implementieren diese Klassen aber auch IFooable und delegieren in den entsprechenden Methoden an DefaultFooable.
Warum das besser ist also direkt mit extends davon zu erben, kannst Du in diversen OO-Büchern lesen. Du hast bei direkter Vererbung von Implementierungscode im Gegensatz zur reinen Schnittstellenvererbung eine viel stärkere Kopplung zwischen den Klassen. Eine Klasse, die ein anderes Objekt per Komposition benutzt ist nicht so stark abhängig von diesem Objekt wie eine Klasse, die von einer anderen Code erbt. Schonmal versucht, eine Klassenhierarchie zu warten, die sich über 10+ Hierarchiestufen erstreckt?
Sobald Du von einer konkreten Klasse erbst, bist Du für immer und ewig an die konkrete Implementierung der Oberklasse gebunden. Nutzt Du hingegen Komposition, kannst Du die Implementierung problemlos auch zu einem späteren Zeitpunkt noch austauschen.
Bei der reinen Schnittstellenvererbung kann hingegen wenig schief gehn. Es ist schade, dass Du Dich mit dem Interface-Konzept nicht anfreunden kannst. Das Konzept ist nämlich insofern genial weil es die Möglichkeit mit sich bringt, Spezifikationen zu schreiben fernab von Implementierungsdetails. In der Java-Welt gibts haufenweise Sun-Spezifikationen, die alleine auf Interfaces basieren (z.B. JPA, JMX, ...).
Wie macht Ihr das im Team, wenn z.B. Schicht 1 etwas von Schicht 2 benötigt? Man definiert ein Interface. Das ist in kürzester Zeit geschrieben und man kann loslegen.
-
byto schrieb:
Du musst keinen Code Copy-Pasten. Ich erklärs nochmal: Du definierst ein Interface IFooable und schreibst die Standard-Implementierung DefaultFooable implements IFooable. Nun möchtest Du Fooables schreiben, die diese Standardimplementierung nutzen. Nun leitest Du aber nicht direkt mit extends von DefaultFooable ab sondern nutzt DefaultFooable in diesen Klassen per Komposition. Zusätzlich implementieren diese Klassen aber auch IFooable und delegieren in den entsprechenden Methoden an DefaultFooable.
OMG, dann habe ich einen sinnlosen Member der von einer reinen logischen Sicht keinen Sinn macht.
vector hat ein serializable.
Klingt komisch. Ist komisch.
Warum das besser ist also direkt mit extends davon zu erben, kannst Du in diversen OO-Büchern lesen. Du hast bei direkter Vererbung von Implementierungscode im Gegensatz zur reinen Schnittstellenvererbung eine viel stärkere Kopplung zwischen den Klassen. Eine Klasse, die ein anderes Objekt per Komposition benutzt ist nicht so stark abhängig von diesem Objekt wie eine Klasse, die von einer anderen Code erbt. Schonmal versucht, eine Klassenhierarchie zu warten, die sich über 10+ Hierarchiestufen erstreckt?
Danke aber genau hier unterscheiden sich unsere Vorstellungen.
Für mich ist ein Interface nicht durch Java definiert.Erkläre mir mal genau warum es so toll ist member einzubauen die meistens komplett nutzlos sind, anstatt direkt polymorphie zu verwenden. Warum muss ich ein DefaultFoo mitschleppen wenn ich das garnicht brauche?
mal abgesehen davon, dass ein DefaultFoo nur sehr eingeschränkten nutzen hat, da er ja keinen Zugriff auf meinen State bekommt.
Das lustigste Beispiel ist hier wohl das Java Serializeable. Das ist ein interface dass implementierungen anbietet. Nur dass man statt foo.serialize() lieber serialize(foo) schreibt um eben das große Probleme zu umgehen dass man keinen Code in interfaces haben kann.
Aber ob ich foo(serialize) oder serialize(foo) schreibe ist komplett egal von einem OO standpunkt aus.
Sobald Du von einer konkreten Klasse erbst, bist Du für immer und ewig an die konkrete Implementierung der Oberklasse gebunden. Nutzt Du hingegen Komposition, kannst Du die Implementierung problemlos auch zu einem späteren Zeitpunkt noch austauschen.
Das ist falsch. Wozu habe ich polymorphie?
Bei der reinen Schnittstellenvererbung kann hingegen wenig schief gehn. Es ist schade, dass Du Dich mit dem Interface-Konzept nicht anfreunden kannst.
Es kann das selbe schief gehen was bei vererbung einer konkreten Klasse passieren kann. Ich bin nämlich fix an das Interface gebunden und daher an die semantik. Selbes bei einer vererbung einer konkreten Klasse. Die implementierung auszutauschen ist _nie_ ein problem - die schwierigkeit ist eine semantik auszutauschen.
Ich kann mich übrigens sehr wohl mit Interfaces anfreunden - nur habe ich ein Problem mit der Java Definition von Interfaces und finde die C++ Definition einfach besser. Deshalb rede ich von Konzepten damit ihr nicht automatisch die Java Definition annehmt.
Das Konzept ist nämlich insofern genial weil es die Möglichkeit mit sich bringt, Spezifikationen zu schreiben fernab von Implementierungsdetails. In der Java-Welt gibts haufenweise Sun-Spezifikationen, die alleine auf Interfaces basieren (z.B. JPA, JMX, ...).
Wie macht Ihr das im Team, wenn z.B. Schicht 1 etwas von Schicht 2 benötigt? Man definiert ein Interface. Das ist in kürzester Zeit geschrieben und man kann loslegen.Und wo beisst sich das mit meiner Definition?
Nirgendwo.Ich kann Schnittstellen implementieren die keinen Code enthalten - das ist absolut kein Problem. Die schwierigkeit tut sich erst auf, wenn ich schnittstellen habe die Code enthalten. Man kann den Java serializable weg gehen, aber dann verliert man polymorphie oder man geht den copy&paste weg der einfach nur bescheuert ist oder man definiert member die komplett sinnlos sind.
Wenn man Interfaces nicht so restriktiv behandelt, habe ich _alle_ Vorteile von Java Interfaces und _alle_ Vorteile von c++ Klassen. Der Nachteil ist lediglich etwas selbstdisziplin. Aber dass das augenauswischerei ist, dass zu erkennen dazu muss man sich nur einmal mittelmäßigen java code ansehen wo 100.000 interfaces implementiert werden.
Schlechten Code kann ich immer schreiben und für guten Code muss ich mich immer konzentrieren...
-
Shade Of Mine schrieb:
OMG, dann habe ich einen sinnlosen Member der von einer reinen logischen Sicht keinen Sinn macht.
Wenn Du nicht verstehen kannst, warum es eben häufig sinnvoller ist, an einen bei Bedarf austauschbaren Member zu delegieren, statt eine konkrete Klasse zu erweitern, dann brauchen wir an dieser Stelle eigentlich gar nicht weiter diskutieren.
Es gibt genug Literatur, die sich damit beschäftigt. Vielleicht solltest du die dann erstmal lesen.
-
Shade Of Mine schrieb:
Erkläre mir mal genau warum es so toll ist member einzubauen die meistens komplett nutzlos sind, anstatt direkt polymorphie zu verwenden. Warum muss ich ein DefaultFoo mitschleppen wenn ich das garnicht brauche?
mal abgesehen davon, dass ein DefaultFoo nur sehr eingeschränkten nutzen hat, da er ja keinen Zugriff auf meinen State bekommt.
Wie erhaelt den die Basisklasse einen Zugriff auf den State der Unterklasse?
class Foo extends DefaultFoo { Bar state; }
class Foo implements Fooable { private DefaultFoo deffoo; Bar state; }
In beiden Faellen kann doch DefaultFoo nicht auf den State von Foo zugreifen?
-
Irgendwie passende Blog-einträge zum Thema:
http://www.berniecode.com/writing/inheritance/
http://weblog.raganwald.com/2008/03/is-is-has.html
-
DEvent: die default-implementation als basisklasse kann von der abgeleiteten klasse überschriebene methoden aus der endgültigen implementierung verwenden. das kompositum nicht.
/edit: http://www.berniecode.com/writing/inheritance/ - genau von da geht shade of mine doch aus - mehrfach erben von konkreten klassen.
-
Ich möchte Mehrfachvererbung zumindestens in C++ nicht missen. Nicht wegen den klassischen Pro/Contras der Umsetzung wegen, sondern auf Grund der Templateprogrammierung um Klassen konfigurierbar zu machen (Wird beispielsweise in der Loki-Bibliothek intensiv verwendet).
Ganz davon abgesehen halte ich Mehrfachvererbung unter Umständen durchaus für sinnvoll, wobei ich es dennoch nur sehr selten und nur unter Abwägung der Konsequenzen ensetzen würde.
cu André
-
DEvent schrieb:
Wie erhaelt den die Basisklasse einen Zugriff auf den State der Unterklasse?
Reflection und Method Calls. Ich kann ja einfach eine Methode aufrufen die ich in der Basisklasse als virtuell (uU pure virtual) definiert habe. Und Reflection ist auch oft ein heisser Tip.
byto schrieb:
Wenn Du nicht verstehen kannst, warum es eben häufig sinnvoller ist, an einen bei Bedarf austauschbaren Member zu delegieren, statt eine konkrete Klasse zu erweitern, dann brauchen wir an dieser Stelle eigentlich gar nicht weiter diskutieren.
Es gibt genug Literatur, die sich damit beschäftigt. Vielleicht solltest du die dann erstmal lesen.Erklär mir mal warum. Nicht aus technischer Sicht, sondern aus logischer sicht.
Wie kann ich in UML erklären, dass ein vector ein DefaultSerializeable hat. Vorallem: vector ist ein serializable (oder implementiert serializable schnittsetlle) UND hat ein DefaultSerializable, dass vector aber garnicht verwendet, weil das ein überbleibsel aus der Vererbung von Container ist.
So ein Design ist ausserhalb der Java-Interfaces Welt einfach nur Schrott. Und ich würde es nichtmal in Java umsetzen. Vorallem da es ja nichtmal immer technisch möglich ist - und logisch ist es sowieso nicht.
Aber bitte, erkläre mir ganz einfach warum ISerializable keine Defaultimplementierung anbieten darf. In Java definiert Serializable hardcoded was es macht - ich kann mich nicht hineinhängen wenn ich will (zB wenn ich meine Klasse verschlüsseln will oder ähnliches). Warum ist es untragbar dass ich serializable customizen will? Java geht zB den Ansatz über Reflection um statische Member der Klasse auszulesen um ein customizing zu ermöglichen. Ist das wirklich der beste Ansatz und polymorphie hat hier garnichts verloren?
-
@Shade Of Mine - Serializable ist ein Marker-Interface und hat sonst nicht viel mit den konventionellen Interfaces zu tun. Das ist ein Spezialfall, den Java unter der Haube benutzt. Ob das schön umgesetzt ist oder nicht, ist eine Frage für sich und hat nichts mit dem Vererbungs- und Interface-Konzept ansich zu tun. Also keine Ahnung, warum Du dieses Thema hier immer wieder auspackst!?
Es geht mir im übrigen auch überhaupt nicht um Java. Das von mir angesprochene ist sprachunabhängig und betrifft alle OO-Sprachen. In Java gibts halt ein extra Sprachmittel, um Schnittstellen zu definieren. In C++ erreicht man den gleichen Effekt wohl durch abstrakte Typen, die nur abstrakte Methoden haben und keine Implementierung beinhalten. Das war aber überhaupt nicht der Punkt.
Der Punkt ist, dass Vererbung von konkreten Code (egal ob Mehrfach oder Einfach) häufig zu extrem schlecht wartbarem Code führt und keine lose Kopplung fördert, wie es bei Komposition der Fall ist.
-
byto schrieb:
@Shade Of Mine - Serializable ist ein Marker-Interface und hat sonst nicht viel mit den konventionellen Interfaces zu tun. Das ist ein Spezialfall, den Java unter der Haube benutzt. Ob das schön umgesetzt ist oder nicht, ist eine Frage für sich und hat nichts mit dem Vererbungs- und Interface-Konzept ansich zu tun. Also keine Ahnung, warum Du dieses Thema hier immer wieder auspackst!?
Eben weil es ein wunderschönes Beispiel dafür ist, dass die Java Interfaces nicht jedes Problem lösen können das ohne der Interface Restriktion trivial wäre.
In C++ erreicht man den gleichen Effekt wohl durch abstrakte Typen, die nur abstrakte Methoden haben und keine Implementierung beinhalten. Das war aber überhaupt nicht der Punkt.
Der Punkt ist, dass viele Leute, vorallem wenn man Java programmiert hat, das Konzept von Interfaces komplett falsch verstehen.
Deshalb darf man nicht in Interface und Klasse trennen - zumindest nicht so wie es Java macht.
-
Shade Of Mine schrieb:
Der Punkt ist, dass viele Leute, vorallem wenn man Java programmiert hat, das Konzept von Interfaces komplett falsch verstehen.
Deshalb darf man nicht in Interface und Klasse trennen - zumindest nicht so wie es Java macht.Verstehe nicht, was Du meinst. Was versteht wer Deiner Meinung nach falsch und wie?
In meinen Augen macht Java es genau richtig: Java-Interfaces erlauben keine Implementierung, also werden die Entwickler in die richtige Richtung gedrückt, Schnittstellen-Spezifikation und Implementierung voneinander zu trennen.
-
byto wech schrieb:
Shade Of Mine schrieb:
Der Punkt ist, dass viele Leute, vorallem wenn man Java programmiert hat, das Konzept von Interfaces komplett falsch verstehen.
Deshalb darf man nicht in Interface und Klasse trennen - zumindest nicht so wie es Java macht.Verstehe nicht, was Du meinst. Was versteht wer Deiner Meinung nach falsch und wie?
In meinen Augen macht Java es genau richtig: Java-Interfaces erlauben keine Implementierung, also werden die Entwickler in die richtige Richtung gedrückt, Schnittstellen-Spezifikation und Implementierung voneinander zu trennen.Und ich warte immer noch auf eine logische Begründung warum vector ein DefaultSerializable besitzt welches ein ISerializable ist und vector selbst ist ebenfalls ein Serializable. Warum ist diese is-a + has-a beziehung soviel besser als eine reine is-a beziehung?
ich will nichts von implementierungsdetails wissen - ich will eine logische begründung haben wie du ein serializable implementieren willst. In Java ist das nicht ohne hacks möglich. Und sobald man hacken muss ist das design imho fehlerhaft. Und man kann nichtmal alles hacken - ich kann zB meine serialisierten daten nicht verschlüsseln oder mich anders serialisieren als java es will.
Und genau das ist der Punkt: java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.
-
camper schrieb:
Der Standard schreibt hinsichtlich reinterpret_cast so gut wie überhaupt nichts vor, sofern die betreffenden Typen (wie in unserm Falle) keine PODs sind und auch nicht union-Member sein können. Das trifft so auch auf einfache Vererbung zu.
Nein, so meinte ich das nicht. Ich hätte da gar nicht reinterpret_cast benutzen sollen, ich wollte das nur in einer Zeile formulieren.
So war das gemeint:Derived* d = 0; Base* b = static_cast <Base*> (d);
Garantiert der Standard, daß b hier 0 ist, auch wenn Base die zweite Basisklasse von Derived ist? Oder muß ich schreiben
Base* b = d ? static_cast <Base*> (d) : 0;
?
Falls ja - was ich sehr hoffe - dann macht die Mehrfachvererbung wenigstens in diesem Punkt nur meinem Compiler das Leben schwer.camper schrieb:
Falls der Compiler alles richtig macht, spielt das Layout keine Rolle - dieser Code hat in jedem Falle wohldefiniertes Verhalten. Wir können gerne diskutieren, dass die Compilerunterstützung hier möglicherweise nicht immer genügt.
Das heißt, der Standard definiert das erwartete Verhalten für diese Situation? Dann wird es ja dringend Zeit für ein paar Bugreports.
camper schrieb:
Ja, ich bestreite nicht, dass in der Praxis viele seltsame Dinge gemacht werden. Ich sehe trotzdem nicht, wie das deine Position hinsichtlich des diskutierten Sprachfeatures unterstützt.
Insofern, als daß ich glaube, daß der Verzicht auf die Möglichkeit der Mehrfachvererbung (für Shade: der Mehrfachvererbung von konkreten Klassen) viel Unfug hätte verhindert und allerlei sprachlicher Ballast insbesondere bei Methodenzeigern hätte vermieden werden können.
Aber wahrscheinlich komme ich da den Grundidealen von C++ ohnehin etwas zu nahe. Das Verhängnis dieser Sprache wird wahrscheinlich die ins Unermeßliche steigende Komplexität, die zu beherrschen immer schwieriger wird und für Unternehmen kaum bezahlbar bleibt.
-
Shade Of Mine schrieb:
Und genau das ist der Punkt: java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.
Lies mal Literatur zu dem Thema und Du wirst feststellen, dass Du mit dieser Meinung so ziemlich alleine darstehst.
-
byto wech schrieb:
Shade Of Mine schrieb:
Und genau das ist der Punkt: java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.
Lies mal Literatur zu dem Thema und Du wirst feststellen, dass Du mit dieser Meinung so ziemlich alleine darstehst.
Wenn man schon jemand sagt, er soll was nachlesen, sollte man ihn auch einen Literaturhinweis geben, am besten mit der entsprechenden wichtigen Seitenzahl. Alles andere ist nur rumgerede.
-
[quote="Artchi]Wenn man schon jemand sagt, er soll was nachlesen, sollte man ihn auch einen Literaturhinweis geben, am besten mit der entsprechenden wichtigen Seitenzahl. Alles andere ist nur rumgerede.[/quote]
Das sagt der richtige!
-
Shade Of Mine schrieb:
java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.
Darf ich mal fragen, mit welcher Art Softwareentwicklung du dich beschäftigst?
Benutzt du Java in größeren Projekten, oder eher nur C++? Kennst du Applikationframeworks wie z.B. Spring? EJBs etc.?
-
Artchi schrieb:
byto wech schrieb:
Shade Of Mine schrieb:
Und genau das ist der Punkt: java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.
Lies mal Literatur zu dem Thema und Du wirst feststellen, dass Du mit dieser Meinung so ziemlich alleine darstehst.
Wenn man schon jemand sagt, er soll was nachlesen, sollte man ihn auch einen Literaturhinweis geben, am besten mit der entsprechenden wichtigen Seitenzahl. Alles andere ist nur rumgerede.
Eigentlich dachte ich nicht, dass man das Offensichtliche auch noch belegen muss, aber bitte. Ich zitiere mal aus dem wohl bekanntesten Werk "Entwurfsmuster" der Gang of Four. In Abschnitt 1.6.4 werden zwei Prinzipien kurz, knapp und präzise folgendermaßen zusammengefasst:
1.) Programmiere auf Schnittstellen hin, nicht auf eine Implementierung
2.) Ziehe Objektkomposition der Klassenvererbung vorEdit: Bruce Eckel schreibt ähnliches in Thinking in Java, genauso wie Martin Fowler. Aber da hättet Ihr sicherlich wieder Kritik geübt, weil es ein Java-Buch ist (auch wenn sich der entsprechende Teil auf OOP im Allgemeinen bezieht). Das o.g. Buch benutzt aber C++ und Smalltalk als Referenzsprachen für die Codebeispiele, insofern bin ich nun auf weitere Kommentare gespannt.
-
audacia schrieb:
Derived* d = 0; Base* b = static_cast <Base*> (d);
Garantiert der Standard, daß b hier 0 ist, auch wenn Base die zweite Basisklasse von Derived ist?
ja. Das geht ja auch implizit (und static_cast führt die Konvertierung immer so aus, wie es implizit geschehen würde, wenn eine implizite Konvertierung existiert). Ein Nullzeiger bleibt ein Nullzeiger. Bei reinterpret_cast ist das nicht unbedingt so (in der Praxis wohl schon, aber egal...)
audacia schrieb:
camper schrieb:
Falls der Compiler alles richtig macht, spielt das Layout keine Rolle - dieser Code hat in jedem Falle wohldefiniertes Verhalten. Wir können gerne diskutieren, dass die Compilerunterstützung hier möglicherweise nicht immer genügt.
Das heißt, der Standard definiert das erwartete Verhalten für diese Situation? Dann wird es ja dringend Zeit für ein paar Bugreports.
Möglicherweise auch nur eine Frage von Compilerschaltern. Visual C++ etwa implementiert Zeiger auf Memberfunktionen per default nicht korrekt (aus (Speicher-)Effizienzgründen), dies könnte in diesem Falle zu Fehlern führen (schau dir die /vmx-Schalter an). Das ist dann allerdings ein Problem einer nicht standardkonformen Erweiterung.
-
byto schrieb:
Ich zitiere mal aus dem wohl bekanntesten Werk "Entwurfsmuster" der Gang of Four.
Da hier auch Seitenzahlen verlangt wurden, bitte sehr:
Seite 23-24: Klassen- versus Schnittstellenvererbung, Prgrammieren auf eine Schnittstelle hin, nicht auf eine Implementierung
Seite 26ff.:Vererbung versus Komposition, Delegation