Braucht man Mehrfachvererbung



  • 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?





  • 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 vor

    Edit: 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



  • Danke! Und, war es so schwer die Quelle zu nennen? 🙄 Also!
    Übrigens sind die Ratschläge auch in jedem anderen C++-Buch a la "Effektiv C++ programmieren" nachzulesen. Ändert nichts daran, das wenn ich pure virtual classes brauche, diese benutze. Ist mir egal obs in anderen Sprache mit dem Schlüsselwort interface gelöst wird. In C++ habe ich das Schlüsselwort interface nicht... und jetzt?



  • java muss hier so häßlich sein weil die trennung interface/klasse falsch ist.

    Sehe ich nicht so.
    Eine Klasse in den gängigen Sprachen ist zwei Dinge: eine Bauvorschrift für Objekte und ein Typ. Der Typ dient dazu, dass das Typsystem zur Compilezeit sicherstellen dass keine Typfehler auftreten (da C++ ein Schwaches Typsystem hat ist das natürlich nicht unbedingt konsequent möglich).

    Gehen wir mal von einer hypothetischen Sprache aus, die gewisse Ähnlichkeiten zu Java/C++/... hat:

    class Foo {
       def methode() : void
       { ... }
       def weitereMethode() : void
       { ... }
       def undNocheine() : void
       { ... }
    }
    

    Foo ist eine Bauvorschrift für Objekte die folgenden Typ haben:

    type {
       methode : void -> void;
       weitereMethode : void -> void;
       undNocheine : void -> void;
    }
    

    Natürlich ist jees Objekt, dass nach der Vorschrift Foo erstellt wurde Subtyp der Typen

    type {
       methode : void -> void;
       weitereMethode : void -> void;
    }
    
    type {
       methode : void -> void;
       undNocheine : void -> void;
    }
    
    type {
       weitereMethode : void -> void;
       undNocheine : void -> void;
    }
    
    type {
       methode : void -> void;
    }
    
    type {
       weitereMethode : void -> void;
    }
    
    type {
       undNocheine : void -> void;
    }
    

    und

    type {}
    

    Jetzt kann ich soetwas machen:

    bar : { 
       methode : void -> void;
       weitereMethode : void -> void;
       undNocheine : void -> void;
    } = Foo();
    

    Natürlich kann man Typen Namen geben, wodurch das ganze etwas übersichtlicher wird:

    type Bar = { 
       methode : void -> void;
       weitereMethode : void -> void;
       undNocheine : void -> void;
    } 
    
    bar : Bar = Foo();
    

    In den üblichen Sprachen ist der Name der Klasse gleichzeitig auch ein Name für den Typ der Objekte, die durch die Bauvorschrift erstellt werden.

    foo : Foo = Foo();
    

    Foo wird hier einmal als Typ verwendet und einmal als Bauvorschrift.

    Ich kann eine weitere Bauvorschrift schreiben, die Objekte des selben Typserstellt:

    class Baz {
       def methode() : void
       { ... }
       def weitereMethode() : void
       { ... }
       def undNocheine() : void
       { ... }
    }
    

    Auch wenn die Implementierung verschieden ist, ist der Typ der erstellbaren Objekte der selbe.

    type {
       methode : void -> void;
       weitereMethode : void -> void;
       undNocheine : void -> void;
    }
    

    Wenn ich jetzt eine Funktion schreiben will, die zu ihrer Asführung vorrausetzt, dass ihr Argument eine bestimmte Methode besitzt, dann könnte das so aussehen:

    def funktion(argument : { methode : void -> void })
    { ... }
    

    Da der Typ des Arguments ein Subtyp des Typs der Objekte ist, die durch Foo und die durch Baz erstellt werden, kann ich Objekte die nach beiden Bauvorschriften erstellt wurden an "funktion" übergeben

    Soweit so gut. Bisher bin ich von strukturellem Subtyping ausgegangen. Java/C++ verwenden hingegen ein nominatives Typsystem. Die Typen haben quasi zusätzlich ein Namen. Damit ein Typ als Subtyp eines anderen gilt muss nun explizit angegeben werden, dass der Typ mit diesem Namen Subtyp des Typs mit jenem Namen ist.

    named_type X {
       methode : void -> void
    }
    named_type Y {
       methode : void -> void
    }
    named_type Z {
       methode : void -> void
       weitereMethode : void -> void
    }
    

    Obwohl X und Y beide strukturell gleich sind gelten sie als inkompatibel. Auch Z gilt nicht als Subtyp von X oder Y.

    named_type X2 {
       methode : void -> void
    }
    named_type Y2 is_subtype_of X2 {
       methode : void -> void
    }
    

    Erst durch diese explizite Angabe ist Y2 Subtyp von X2.

    Mein Kleines Beispiel mit der Funktion von oben funktioniert in Mainstreamsprachen nicht. Wie gesagt haben sie ausschließlich nominative Typsysteme. Typen müssen also Namen haben

    named_class NamedFoo {
       def methode() : void
       { ... }
    }
    
    named_type Dummy { 
       methode : void -> void 
    }
    def funktion(argument : Dummy)
    { ... }
    
    funktion(Foo());  // Fehler
    

    In einem nominativen Typsystem funktioniert das Beispiel nicht, weil nicht explizit angegeben wurde, dass der Typ Foo Subtyp des Typs Dummy ist; die Bauvorschrift Foo erstelt aber Objekte des Typs Foo.

    named_type Dummy { 
       methode : void -> void 
    }
    named_class NamedFoo is_subtype_of Dummy {
       def methode() : void
       { ... }
    }
    
    def funktion(argument : Dummy)
    { ... }
    
    funktion(NamedFoo());  // OK
    

    Dummy und NamedFoo beschreiben Typen der selben Struktur. Deswegen kommt man schnell zu dem Schluss einfach sowas zu schreiben:

    named_class NamedFoo {
       def methode() : void
       { ... }
    }
    
    def funktion(argument : NamedFoo)
    { ... }
    
    funktion(NamedFoo());  // OK
    

    In meinem anfänglichen Beispiel wo ich noch von einem strukturellen Typsystem ausgegangen bin konnte ich eine weitere Klasse Baz schreiben, und dann Objekte die nach dieser Bauvorschrift erstellt wurden ebenfalls an die Funktion übergeben.

    Genau aus diesem Grund führt man den zunächst nutzlos erscheinenden Typ Dummy ein, damit man auch anderen die Chance gibt Objekte mit anderem Verhalten zu erstellen, und sie an funktion zu übergeben.

    -------------

    Interfaces sind einfach Typen, Klassen sind Bauvorschriften und Typen, "Abstrakte Klassen" sind unvollständige Bauvorschriften und Typen.

    Vererbung ist eine Art Copy&Paste von Bauvorschriften und gleichzeitig das Herstellen einer (nominativen) Subtypbeziehung.

    Das ist zumindest eine Betrachtungsweise des ganzen.



  • tfa schrieb:

    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.?

    Ich benutze die Tools die ich effektiv sind. Sei es jetzt Java, .NET, C, C++, PHP oder VB. (aktuell gehen die Projekte wieder Richtung C++ (MFC) vorher aber laengere Zeit fast nur Java).

    Nur weil ich Tools effektiv einsetzen kann heisst es noch lange nicht dass ich sie gut finden muss. Und EJB, bitte. Ist es dein ernst dass EJB gutes Design ist? Praktisch - keine Frage, aber die ganzen getter/setter - das tut einem OOD'ler einfach nur weh...

    Ich setze zB die MFC oft ein. Ist sie schoen designed? Nein. Macht es Spass mit ihr zu programmieren? nein. Ist sie effektiv? Ja.

    Und nur das zaehlt in der grossen weiten welt - aber gerade hier im Forum kann man durchaus mal den Leuten etwas beibringen - naemlich dass Java nicht das non plus ultra ist.

    Java hat viele Sachen probiert - zB checked Exceptions. Manche Sachen funktionieren gut, manche weniger gut. Aber man kann mit Java arbeiten ohne dass sich einem der Magen umdreht. Aber Java bemueht sich einfach zu sehr den Programmierer am schlechten Code schreiben zu hindern (was natuerlich nicht klappt) dass einige schoene Designs einfach nicht moeglich sind.

    Diese Trennung Interface/Klasse ist super fuer anfaenger, denn wie oft haben wir schon gesehen dass man eine Klasse Autos von Auto und std::vector ableiten will, anstatt einen std::vector<Auto> zu erstellen? Java verbietet soetwas by design. Super Sache ansich. Aber wenn man mal tiefer einsteigt, dann beengt einen Java. Weil ich will nicht Autos von vector und Auto erben lassen, ich will nicht von mehreren konkreten Klassen erben (meistens jedenfalls nicht) - ich will ja von interfaces erben - nur eben nicht von Java Interfaces.

    AbstractFoo, DefaultFoo, IFooable - in anderen Sprachen habe ich dafuer genau eine Klasse und habe kein bisschen komplexere hierachien.

    Was euch so verwirrt ist, dass fuer mich ein interface mehr ist als das was Java als interface definiert.

    Wenn ich mir die 2 Links zu dem GoF Buch ansehe, dann stelle ich fest dass du entweder das Buch nicht verstanden hast, oder das was ich sage.

    Ich sehe da nirgendwo die Java Definition eines Interfaces. Die verlinkten stellen gehen sogar einen Schritt weiter als ich hier aussagen wollte.

    Schnittstellenvererbung sagt aus wann ein Objekt anstelle eines anderen verwendet werden kann

    Ich nenne es hier Konzepte - um mich von den Java Interfaces zu distanzieren. Aber Namen sind Schall und Rauch. Nur weil eine Klasse abstrakt ist, heisst es noch lange nicht dass sie nicht verhalten haben kann. Die Schnittstelle wird definiert und diese Schnittstelle darf ruhig verhalten haben.

    Implementierungsvererbung wie es dort so schoen heisst, ist die Implementierung eines Objektes mit Hilfe eines anderen.

    Ich sehe da nicht den kleinsten widerspruch zu dem was ich bisher gesagt habe. Ich will ja nicht einen vector mit hilfe von serializable implementieren - ich will dass vector die schnittstelle serializable definiert (und nebenbei will ich ein vernuenftiges default verhalten haben, dass ich ueberschreiben kann wenn noetig).

    byto schrieb:

    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 vor

    Und wieder jemand der es nicht verstanden hat. Ich habe nie etwas gegen Schnittstellen gesagt - ich rede gegen die Java Definition einer Schnittstelle.

    Edit: 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.

    In einem Java Buch geht man auf Java ein, in einem C++ Buch auf C++.

    Du wirst in keinem guten C++ Buch die Java Definition von Interfaces finden - in Smalltalk Buechern schon garnicht (Smalltalk hat nichtmal Interfaces als Konzept). Dennoch ist Smalltalk eine wunderschoene Sprache wie viele OOP'ler dir gerne bestaetigen. Obwohl Smalltalk keine Java Interfaces bietet.

    Auch bin ich sofort dabei wenn es um komposition statt vererbung geht, aber komposition ist nicht immer moeglich. zB ein DefaultFoo mittels komposition zu implementieren ist einfach falsch. Die Sprache kann einen dazu zwingen, aber es ist design technisch eine katastrophe. Ich verliere ja den kompletten zugriff, kann mich in keine algorithmen reinhaengen, keine werte setzen, garnichts.

    Erklaer mir bitte einfach nur folgenden Punkt:

    struct Serializable {
      virtual string serialize() {
        return reflection.getAllMemberData(this);
      }
    };
    
    class Vector : public Serializable, Container {
    //...
    };
    

    Warum ist das schlechtes Design?

    Oder wenn dir Serializable nicht gefaellt - nimm Movable wie es zB MOJO bietet.
    MOJO ist ein interessanter Ansatz aus C++ Sicht, aber betrachten wir einmal nur das Design:

    Ich definiere eine Klasse als Movable indem ich sie von Movable ableite. Movable definiert nichts anderes als eine Schnittstelle, enthaelt aber Code um die Klasse wirklich Movable zu machen. Ansonsten waere es ja ein Flag (wie das Java Serializable und sobald wir Interfaces als Flags verwenden, verlieren wir Polymorphie).

    Warum ist es nun schlecht einen Movable vector zu haben? Ich wuerde zB von std::vector erben und von movable. Leider haben wir keine static vererbung daher ist es etwas gefaehrlich weil vector keine gute basisklasse ist, aber wir koennen uns ja vorstellen dass dies dennoch der Fall ist.

    Eine Movable und Serializable Klasse zu haben, finde ich auch nicht abwegig. In Java nicht elegant moeglich, weil ich dann Copy&Paste machen muesste. Denn DefaultSerializable kann kein Member sein - hier brauche ich Vererbung. Also haben wir ploetzlich folgenden Code:

    struct Serializable {
      virtual string serialie();
      //use: "return reflection.getAllMemberData(this);"
    };
    
    class Vector : public Serializable, Vector {
    public:
      void string serialize() {
        return reflection.getAllMemberData(this);
      }
    };
    

    Oder vielleicht noch schoener mit einem DefaultSerializableBehavior Objekt?

    struct DefaultSerializableBehavior {
      string serialize(Object* o) {
        return reflection.getAllMemberData(o);
      }
    };
    class Vector : public Serializable, Vector {
    private:
      DefaultSerializeableBehavior s;
    public:
      void string serialize() {
        return s.serialize(this);
      }
    };
    

    Von einem OOD standpunkt aus, ist keins davon wirklich schoen.
    funktionieren tun sie alle, aber warum ist die kuerzeste und direkteste variante die schlechteste?

    Der Grund warum man gegen Mehrfachvererbung von konkreten Klassen ist, ist der dass die komplexitaet des Codes ansteigt. Das ist aber mit meinen "Konzepten" kein bisschen mehr der Fall als bei Interfaces. Die Gefahr dass sich Funktionen ueberlagern ist genauso gegeben wie mit Interfaces. Wenn 2 unterschiedliche Interfaces den selben Namen fuer eine Methode verwenden, hat man sowieso ein Problem. Denn entweder ist das Design schlecht und es sollten 3 Interfaces oder 1 Interface sein oder aber die Methode hat unterschiedliche verhaltensdefinitionen.

    Und sonst? Welche Nachteile von mehrfachvererbung habe ich denn bei meinen Konzepten die man mit Java Interfaces nicht hat? Eine ganz einfache Frage.


Anmelden zum Antworten