Braucht man Mehrfachvererbung



  • rüdiger schrieb:

    Ohne Mehrfachvererbung sind einige Probleme nicht OO-mäßig lösbar.

    Ein einfaches Beispiel wäre eine OO-Modelierung des folgenden Problems: Man hat einen unbewegten Ball, man hat einen hüpfenden Ball und einen Ball der seine Farbe ändert. Wie baut man in die Hierachie nun einen Ball ein, der hüpft und seine Farbe ändert?

    Decorator?



  • man sollte zwischen mehrfachvererbung (nach c++ art) und implementierung mehrerer interfaces (nach java art) differenzieren. die java variante ist sehr sinnvoll, die c++ variante "echter" mehrfachvererbung überflüssig.



  • thordk schrieb:

    man sollte zwischen mehrfachvererbung (nach c++ art) und implementierung mehrerer interfaces (nach java art) differenzieren. die java variante ist sehr sinnvoll, die c++ variante "echter" mehrfachvererbung überflüssig.

    Ich sehe da keinen relevanten Unterschied.

    Ich muss ehrlich gesagt sogar gestehen, dass ich den Java Ansatz furchtbar finde. Die Idee ansich Interfaces und Klassen explizit zu trennen mag ok sein - aber ich kenne keine Sprache wo man Vererbung so oft einsetzt wie in Java.

    Man muss bei Vererbung IMHO 2 Sachen unterscheiden: man kann von konkreten Klassen erben und von Konzepten (oder wie es oefters genannt wird "Interfaces" - wobei damit nicht die Java Interfaces gemeint sind).

    Es spricht IMHO nichts dagegen wenn ein Konzept fertig implementierte Methoden anbietet. Beispiel "Serializable". Das Konzept Serializable darf ruhig serialize() defaultmaessig implementieren. Das macht die Vererbungshierachie nicht wirklich komplexer. Probleme bekommt man nur, wenn man von 2 konkreten Klassen erbt - zB BunterHuepfenderBall erbt von BunterBall und HuepfenderBall.

    Diese Vererbung ist dann nicht per se schlecht - aber wenn man von konkreten Klassen erbt, muss man aufpassen.



  • Ich finde Mehrfachvererbung (im C++ Stil) grauenhaft und versuche sie zu vermeiden wo es nur geht. Bis jetzt hatte ich auch noch nicht einen Fall, wo es nicht ohne sie ging.



  • mal davon abgesehen, dass mehrfachvererbung schnell zu problemen führen kann (uneindeutigkeiten oder im fall von c++ verdeckung von dekonstruktoren), sorgt sie imo auch ziemlich schnell für sehr eklige "interfaces". hatte mal nen gutes beispiel, bei dem jemand es für ne tolle idee hielt, ein superinterface für sensorenkontrolle zu schaffen, indem er zwei konkrete, inkompatible, implementierungen in einer klasse vereint und auch noch eine matrizenimplementierung dazugeklebt hatte. das hat dazu geführt, dass die doku ständig darauf hinweisen musste, welche methoden auf gar keinen fall nacheinander verwendet werden dürfen und, natürlich, dafür gesorgt hat, dass die anwendung ständig abgeschmiert ist, weil irgendwer die doku nicht gelesen hatte.

    wenn mehrfachvererbung nicht erlaubt wird, können solche grausamen konstrukte gar nicht erst entstehen.



  • Hi,

    Delphi, oder auch DIE typische objektorientierte Sprache Smalltalk kommen ohne aus. Also kanns nicht notwendig sein.
    In der Hand des Profis kanns das eine oder andere eleganter lösen, in der Hand des Anfängers wirds schnell ne Zeitbombe...

    Gruß Mümmel



  • Euer Fehler ist, dass ihr Mehrfachvererbung mit Mehrfachvererbung von konkreten Klassen gleich setzt. Ihr denkt bei Mehrfachvererbung sofort an:

    Fahrzeug
    Auto erbt von Fahrzeug
    Boot erbt von Fahrzeug
    AmphibienAuto erbt von Auto und Boot

    Aber das ist ein sehr sehr seltener Fall. Viel relevanter ist die Situation wo man von Konzepten erbt. Warum sollte ich nicht von nocopyable und Auto und Hashable erben können? Wo ist da auch nur ein funken Problem?

    Das Problem mit dem Interface Ansatz ist ja der, dass man plötzlich Code verdopplung hat. Man hat IFooable und AbstractFoo die beide komplett identisch sind, mit Ausnahme dass AbstractFoo eine implementierung besitzt. Warum kann aber IFooable nicht eine default implementierung annehmen?

    Genau diese Frage ist der Unterschied in der Mehrfachvererbung von Java und C++. In C++ kann man dann natürlich auch komplett blöde Sachen machen: Auto kann von Vogel, Boot, Flugzeug und std::vector erben. Aber der Punkt ist: man muss es nicht tun. Man erbt von Konzepten und nicht von konkreten Klassen. Eigentlich erbt man fast nie von konkreten Klassen...

    Lustigerweise sind Vererbungshierachien in C++ deshalb meistens viel schöner und flacher und vorallem weniger breit als in Java 😉



  • Shade Of Mine schrieb:

    Lustigerweise sind Vererbungshierachien in C++ deshalb meistens viel schöner und flacher und vorallem weniger breit als in Java

    das liegt aber eher daran, dass c++ user davor zurückschrecken und gleich 'designfehler' und laufzeitprobleme wittern, wenn mal geerbt wird oder gar 'virtual' zum einsatz kommt.
    🙂



  • java-freak schrieb:

    Shade Of Mine schrieb:

    Lustigerweise sind Vererbungshierachien in C++ deshalb meistens viel schöner und flacher und vorallem weniger breit als in Java

    das liegt aber eher daran, dass c++ user davor zurückschrecken und gleich 'designfehler' und laufzeitprobleme wittern, wenn mal geerbt wird oder gar 'virtual' zum einsatz kommt.
    🙂

    Keine Frage - das ist eben das Lustige das passiert wenn Paradigmen übernommen werden ohne sie zu verstehen.

    In Java hat man viel Vererbung, Laufzeitpolymorphie wohin man nur schaut - in C++ dagegen fürchtet man sich fast vor einem virtual.

    Es gibt viele solche lustige Erscheinungen - das bedeutet aber nicht dass die Sprache deshalb schlecht sei 😉



  • Also ich benutze dann virtual, wenn ich es brauche. Nicht mehr und nicht weniger.



  • Artchi schrieb:

    Also ich benutze dann virtual, wenn ich es brauche. Nicht mehr und nicht weniger.

    Gute Programmierer programmieren gut 😉

    Es ist lediglich interessant sich durchschnittlichen oder eher schwächeren Code anzusehen. Da strotzt Java vor Vererbung und 100.000 Listener die überall hingeerbt werden und in C++ hat man dagegen nur ein paar functors die durch die Gegend fliegen.



  • Shade Of Mine schrieb:

    Es ist lediglich interessant sich durchschnittlichen oder eher schwächeren Code anzusehen. Da strotzt Java vor Vererbung und 100.000 Listener die überall hingeerbt werden und in C++ hat man dagegen nur ein paar functors die durch die Gegend fliegen.

    Ja, nur in Java hast du nicht sowas wie Functors. Nennt sich in anderen Sprachen glaube ich Delegate? In Java bleibt dir somit nichts anderes übrig als das Listener-Interface zu "vererben".

    Und natürlich finde ich std::tr1::function toll, dann brauche ich keine Vererbung, um eine beliebige (Member-) Function zu verbinden (std::tr1::bind). Ein virtual kann man so natürlich einsparen.

    struct Observer
    {
        void update();
    };
    
    Observer obs;
    std::tr1::function<void (void)> obs_fun = std::tr1::bind(&Observer::update, &obs);
    
    // später:
    obs_fun();  // ruft  obs.update() auf
    

    obs_fun kann natürlich jede andere beliebige (Member-)Function binden.

    EDIT: function und bind kommen auch in C++0x rein, und somit direkt im Namespace std.



  • Shade Of Mine schrieb:

    Es ist lediglich interessant sich durchschnittlichen oder eher schwächeren Code anzusehen. Da strotzt Java vor Vererbung und 100.000 Listener die überall hingeerbt werden und in C++ hat man dagegen nur ein paar functors die durch die Gegend fliegen.

    Sehr objektiver Sprachenvergleich. 👍 🤡



  • Artchi schrieb:

    Ja, nur in Java hast du nicht sowas wie Functors. Nennt sich in anderen Sprachen glaube ich Delegate? In Java bleibt dir somit nichts anderes übrig als das Listener-Interface zu "vererben".

    Schlechter Java Code:

    public class Frame1 extends JFrame implements AListener, BListener, CListener, DListener... {
    ...
      foo.registerFooListener(this);
      bar.registerBarListener(this);
    }
    

    Guter Java Code:

    public class Frame1 extends JFrame {
    ...
      foo.registerFooListener(new FooListener() {
        ...
      });
      bar.registerBarListener(new BarListener() {
        ...
      })
    
    }
    

    aber scheinbar sind wir noch nicht reif genug um über praktiken zu sprechen die sich eingebürgert haben...



  • Du hast Anonyme Klassen... schön, habe ich hier in meinen Projekten auch. Trotzdem ist es Vererbung! Nur das am Ende die spezielle Implementierung keinen Namen hat. Du hast das Interface-Vererben-Problem nicht umgangen, nur verlagert. Von den Interface bin ich immer noch abhängig und Polymorphy ist es trotzdem noch!



  • Artchi schrieb:

    Du hast Anonyme Klassen... schön, habe ich hier in meinen Projekten auch. Trotzdem ist es Vererbung! Nur das am Ende die spezielle Implementierung keinen Namen hat. Du hast das Interface-Vererben-Problem nicht umgangen, nur verlagert. Von den Interface bin ich immer noch abhängig und Polymorphy ist es trotzdem noch!

    Habe ich je gesagt dass das das Problem ist?

    Das Problem sind tiefe und breite Vererbungshierachien.



  • Das grundsätzliche Problem bei der Implementation der Mehrfachvererbung ist doch die Relativität der Objektadresse. Als unmittelbare Konsequenz sind C++-Methodenzeiger für Delegate-Zwecke nahezu unbrauchbar, wie Don Clugston anschaulich darlegt. Auch laufen dadurch sich ergebende Unausweichlichkeiten im Umgang mit Objektzeigern dem intuitiven Verständnis des Programmcodes zuwider. Wie sollte man auf die Idee kommen, daß eine harmlose Anweisung wie

    Base* b;
    Derived* d;
    ...
    b = d;
    

    die Zeigeradresse verändert? Und gesetzt, Derive leite von mehreren Klassen ab, die ihrerseits von Base abstammen, was bei polymorphielastiger Programmierung vorkommen kann: mit welch überflüssiger Verzweiflung reagiert ein Novize auf die dröge Verweigerung des Compilers, dasselbe Stück Codes zu übersetzen?

    Mit Interfaces im herkömmlichen Sinne - so wie sie auch in Delphi und vermutlich in Java existieren - wird dieses Problem vermieden, da sie keine Datenelemente enthalten. Das hat sich in vielen Situationen, ob nun Java, .NET oder COM, als sinnvoll erwiesen (wenngleich in Java ein gewisses Überreizungspotential bestehen mag). Nach einer praktischen und alternativlosen Anwendung der Mehrfachvererbung von C++ jedoch suche ich noch immer vergeblich.



  • Technisch zwar sehr schoen, aber was interessiert mich das als Programmierer?

    Wenn man eine schoene Library wie zB boost oder dergleichen hat, wird die ganze Implementation vor einem versteckt. Deshalb _kann_ ich Member function pointer problemlos benutzen, oder aber functors oder aber den Java Ansatz mit laufzeit Polymorphie.

    Ich habe in C++ also alle 3 Ansaetze die problemlos funktionieren:

    Delegates (methodenzeiger)
    Interfaces (laufzeitpolymorphie)
    functors (compiletime polymorpgie)

    Wo genau ist das jetzt schlecht? Dass bei Methodenzeigern intern furchtbare Sachen geschehen stimmt ja - aber andererseits: was bei einer referenzierung eines Objektes in Java intern passiert, ist auch nicht schoen. Dauernd wird das Objekt umherkopiert - der Zeiger aendert sich also auch 😉

    Interessiert aber Niemanden, da es perfekt versteckt ist.



  • Ohne Mehrfachvererbung sind einige Probleme nicht OO-mäßig lösbar.

    Objektorientierung wird mit nichten durch Vererbung geschweige denn Mehrfachvererbung definiert. Sie setzt sie nicht einmal zwingend voraus.
    Häufig sind Alternativen wie Komposition/Delegation oder sonstige Entwurfsmuster besser (um nicht zu sagen "objektorientierter") als Vererbung.



  • Shade Of Mine schrieb:

    Technisch zwar sehr schoen, aber was interessiert mich das als Programmierer?

    Wie kann ein C++-Programmierer sich ernsthaft eine solche Frage stellen? Noch nie im Assembler-Debugger gelandet?

    Shade Of Mine schrieb:

    Wenn man eine schoene Library wie zB boost oder dergleichen hat, wird die ganze Implementation vor einem versteckt. Deshalb _kann_ ich Member function pointer problemlos benutzen, oder aber functors oder aber den Java Ansatz mit laufzeit Polymorphie.

    Ich habe in C++ also alle 3 Ansaetze die problemlos funktionieren:

    Delegates (methodenzeiger)
    Interfaces (laufzeitpolymorphie)
    functors (compiletime polymorpgie)

    Bekanntermaßen ermöglicht C++ insbesondere mittels TMP die Möglichkeit, so ziemlich jedes Paradigma umzusetzen, sei es noch so schmerzhaft. Die Sprache bietet andererseits, und damit bleibt sie in der Nähe des Ideals von C, sehr wenig Compilermagie. Infolgedessen müssen alle Sprachdefizite durch Bibliotheken ausgeglichen werden. Das erhöht die Kompilierzeiten, die Fehleranfälligkeit, die Uneinheitlichkeit und die Komplexität.

    Um aber dem unbedingten Ideal der Plattformunabhängigkeit zu genügen, offeriert die Standardbibliothek zumeist eher minimalistische Hilfsmittel. Selbst so grundlegende Dinge wie funktionierende Funktionszeiger muß man über Boost nachrüsten (oder selbst schreiben, falls die Boost-Lösung nicht ausreichend performant oder in anderer Hinsicht unzulänglich ist; Stichwort Uneinheitlichkeit). Es wäre so unsagbar einfach gewesen, analog zu Borlands C++Builder-Compilererweiterung __closure ein Schlüsselwort zu spezifizieren, das eine einheitliche Syntax für an ein Objekt gebundene Methodenzeiger einführt. Sie sind wesentlich leichter zu implementieren als Methodenzeiger, außerdem maximal performant. Aber dafür wäre ja ein neues Schlüsselwort notwendig gewesen. Da nimmt man dann lieber den Boost-Workaround in den nächsten Standard auf.

    Das ist natürlich kein Einzelfall. Glücklicherweise gibt es durchaus positive Ansätze im neuen Standardentwurf, z.B. die Neuinterpretation des auto-Schlüsselwortes. Von den produktiven Möglichkeiten einer wirklich innovativen Sprache wie z.B. Chrome ist C++ aber dennoch weit entfernt. Und die Komplexität, die C++0x erreichen wird, ist für einen Neueinsteiger unüberschaubar bis abschreckend.

    Nun bin ich aber ein wenig vom Thema abgekommen 😉
    Kurzum, hast du Mehrfachvererbung schon einmal benötigt? Nein? Dann stimmst du mir sicherlich darin zu, daß es ohne erkennbaren Mehrwert zusätzliche Komplexität verursacht und ein eigentlich ganz praktisches Sprachfeature wie Methodenzeiger verkrüppelt.

    Shade of Mine schrieb:

    Wo genau ist das jetzt schlecht? Dass bei Methodenzeigern intern furchtbare Sachen geschehen stimmt ja - aber andererseits: was bei einer referenzierung eines Objektes in Java intern passiert, ist auch nicht schoen. Dauernd wird das Objekt umherkopiert - der Zeiger aendert sich also auch 😉

    In einer Bytecode-Sprache ist das dann doch noch etwas anderes als in einem besseren C-Derivat 😉


Anmelden zum Antworten