Favour Composition over Inheritance - Aber Wann?



  • Morgen Leute,

    man sagt ja immer wieder das man FCoI anwenden soll wenn moeglich.
    Jetzt stell ich mir die Frage wann es wirklich Sinn macht.

    Aktuell sollen wir ein Projekt fuer die Uni machen wo wir ne einfache Personenhierachie einer Universitaet darstellen sollen (Personen, Studenten, Beschaeftigte, Professoren). Eigentlich ist das ein Paradebeispiel fuer Vererbung, jedoch wollte ich mich mal von der Vererbung loesen und Versuchen es mit Komposition zu loesen. Nur frag ich mich jetzt hier ob es da wirklich Sinn macht, oder ob ich nich doch lieber die Vererbung nehmen soll. Ich will die Komposition da jetzt nicht mit Macht reindruecken, mich wuerde nur mal Interessieren in welchen Faellen denn eine Komposition der Vererbung vorzuziehen ist.
    Das Problem was man doch dann dadurch hat, ist das einem irgendwie die Polymorphie verloren geht und man diese ueber einen anderen Weg reindruecken soll.

    Ich wuerd mich ueber euere Erfahrungen in dem Bereich freuen 🙂



  • Ein Student IST eine Person. Das ist Vererbung.

    Firefighter schrieb:

    mich wuerde nur mal Interessieren in welchen Faellen denn eine Komposition der Vererbung vorzuziehen ist.

    Immer, wenn man nicht bescheid weiß. Siehe Wurstbrot und Supermarkt.

    Manche Leute kommen auf lustige Ideen, wie Person von Adresse erben zu lassen, damit man die Personen in einer Adressliste verwalten kann. Das schreit nach Ärger. Und man kann's den Leuten nicht beibringen. Also lehrt man sie, immer Vererbung zu vermeiden. Man kann ihnen ja kein Computerverbot geben.



  • Du musst hier Vererbung einsetzen. Überhaupt finde ich eine pauschale Aussage wie Favour Composition over Inheritance recht mies. Sie zeugt davon, dass jemand davon ausgeht, dass ihr die Vererbung nie verstehen werdet.

    In gewissen Situationen ist private Vererbung eine bessere Option als manuelle Composition. Aber das wollen die Herren ja nicht wahrhaben :p



  • Firefighter schrieb:

    mich wuerde nur mal Interessieren in welchen Faellen denn eine Komposition der Vererbung vorzuziehen ist.

    Siehs andersrum. Der alte Irrglaube "Objektorientiert = Vererbung" verleitet dazu, erstmal Vererbung als gegeben anzunehmen und sich dann zu fragen "wo wäre Komposition besser?" - so wie du es auch formuliert hast.
    Tatsächlich sollte man andersrum die Komposition als default nehmen und dann fragen "wo sollte ich stattdessen Vererbung benutzen?".

    Die Antwort auf DIE Frage ist dann nämlich duetlich einfacher, allerdings von der Sprache abhängig. Einige Beispiele, die ein deutliches Indiz für Vererbung sind (das heißt nicht, dass Vererbung dann ein Muss ist!):

    1. Polymorphie wird benötigt. (vgl. aber auch Compiletime-Polymorphie durch Templates)
    2. Die Basisklasse unterliegt nicht deiner Verantwortung und du brauchst z.B. Zugriff auf protected Member (d.h. Vererbung als "hack" für unzureichendes Design)
    3. Viele Template-Idioms benutzen Vererbung (z.B. CRTP, spezielle Anwendungen des Barton-Nackmann-Trick, Policy based Design,...)

    In deinem Fall wäre es wohl ein ziemliches Gewürge, keine Vererbung zu benutzen...



  • /rant/ schrieb:

    In gewissen Situationen ist private Vererbung eine bessere Option als manuelle Composition. Aber das wollen die Herren ja nicht wahrhaben :p

    Privat kannste machen, was Du willst.



  • volkard schrieb:

    Ein Student IST eine Person. Das ist Vererbung.

    Das ist ja das was ich meinte. Ich habe schon oft gesehen das in solchen Is-A-Beziehungen dennoch manchmal Komposition angewendet wird. Mir ist auch klar das dieser Fall den der Dozent beschreibt definitiv nach Vererbung schreit, haette halt nur gedacht das man es in diesem Fall auch mal mit Komposition probieren kann.

    Aber so weiss ich immer Bescheid das man definitiv Vererbung benutzen sollte wenn es auch definitiv eine Is-A-Beziehung ist.



  • pumuckl schrieb:

    Firefighter schrieb:

    mich wuerde nur mal Interessieren in welchen Faellen denn eine Komposition der Vererbung vorzuziehen ist.

    Siehs andersrum. Der alte Irrglaube "Objektorientiert = Vererbung" verleitet dazu, erstmal Vererbung als gegeben anzunehmen und sich dann zu fragen "wo wäre Komposition besser?" - so wie du es auch formuliert hast.
    Tatsächlich sollte man andersrum die Komposition als default nehmen und dann fragen "wo sollte ich stattdessen Vererbung benutzen?".

    Die Antwort auf DIE Frage ist dann nämlich duetlich einfacher, allerdings von der Sprache abhängig. Einige Beispiele, die ein deutliches Indiz für Vererbung sind (das heißt nicht, dass Vererbung dann ein Muss ist!):

    1. Polymorphie wird benötigt. (vgl. aber auch Compiletime-Polymorphie durch Templates)
    2. Die Basisklasse unterliegt nicht deiner Verantwortung und du brauchst z.B. Zugriff auf protected Member (d.h. Vererbung als "hack" für unzureichendes Design)
    3. Viele Template-Idioms benutzen Vererbung (z.B. CRTP, spezielle Anwendungen des Barton-Nackmann-Trick, Policy based Design,...)

    In deinem Fall wäre es wohl ein ziemliches Gewürge, keine Vererbung zu benutzen...

    Das find ich Spitze, super Erklaerung und den Blickwinkel hier umzudrehen find ich auch gut, ich glaub das wird mir in Zukunft noch mehr helfen.

    Ich will es nochmal betonen, mir ist selbst klar das ich hier Vererbung einsetzen muss, ich hab das Beispiel jedoch mit Absicht gewaehlt um herauszufinden wann diese Aussage FCoI ueberhaupt anwendung findet.



  • Firefighter schrieb:

    Aber so weiss ich immer Bescheid das man definitiv Vererbung benutzen sollte wenn es auch definitiv eine Is-A-Beziehung ist.

    …und jeder wissen darf, daß eine Is-A-Beziehung herrscht. Also wenn man public vererbt.

    So könnte ich mir einen Ringpuffer vorstellen, der eigentlich ein Array IST, aber noch zwei Zeiger dazu hat und push() und pop(). Es wird auch keiner Widersprechen, wenn man die Beschreibung beginnt mit "Ein Ringpuffer ist ein Array, das…".
    Und schon haben wir den Salat. Welche Regel greift hier? Doch schlichtes FCoI?



  • volkard schrieb:

    Firefighter schrieb:

    Aber so weiss ich immer Bescheid das man definitiv Vererbung benutzen sollte wenn es auch definitiv eine Is-A-Beziehung ist.

    …und jeder wissen darf, daß eine Is-A-Beziehung herrscht. Also wenn man public vererbt.

    So könnte ich mir einen Ringpuffer vorstellen, der eigentlich ein Array IST, aber noch zwei Zeiger dazu hat und push() und pop(). Es wird auch keiner Widersprechen, wenn man die Beschreibung beginnt mit "Ein Ringpuffer ist ein Array, das…".
    Und schon haben wir den Salat. Welche Regel greift hier? Doch schlichtes FCoI?

    Gute Frage, aber sowas wuerde ich schon mit FCoI loesen.



  • Firefighter schrieb:

    Gute Frage, aber sowas wuerde ich schon mit FCoI loesen.

    Andererseits kannst du dann deinen Ringpuffer nicht dort verwenden, wo du auch ein Array verwenden könntest. Wenn die Implementation aber so schön ist, dass du überall dort, wo du ein Array hast, auch einen Ringpuffer verwenden könntest, dann spricht das deutlich für Vererbung.



  • otze schrieb:

    Firefighter schrieb:

    Gute Frage, aber sowas wuerde ich schon mit FCoI loesen.

    Andererseits kannst du dann deinen Ringpuffer nicht dort verwenden, wo du auch ein Array verwenden könntest. Wenn die Implementation aber so schön ist, dass du überall dort, wo du ein Array hast, auch einen Ringpuffer verwenden könntest, dann spricht das deutlich für Vererbung.

    Natuerlich auch wieder korrekt. Ich find es ist sehr schwer diese Aussage zu validieren, mich wundert es halt das es dennoch so oft in vielen Clean-Code Buechern vorkommt und propagiert wird. Mich beschaeftigt die Frage schon lange, hab bisher nur nie ein Beispiel finden koennen.



  • otze schrieb:

    Firefighter schrieb:

    Gute Frage, aber sowas wuerde ich schon mit FCoI loesen.

    Andererseits kannst du dann deinen Ringpuffer nicht dort verwenden, wo du auch ein Array verwenden könntest. Wenn die Implementation aber so schön ist, dass du überall dort, wo du ein Array hast, auch einen Ringpuffer verwenden könntest, dann spricht das deutlich für Vererbung.

    Das ist wieder von der Sprache abhängig. In C++ würde ich das über freie Funktionen und Templates machen. Siehe boost.circular_buffer der sich wie alle anderen Container verhält.



  • Firefighter schrieb:

    mich wundert es halt das es dennoch so oft in vielen Clean-Code Buechern vorkommt

    Tja, die Clean-Code-Bewegung halte ich für eine Pest. 😉
    Sie ruft geradezu danach, mit religiösen Inhalten gefüllt zu werden.



  • otze schrieb:

    Firefighter schrieb:

    Gute Frage, aber sowas wuerde ich schon mit FCoI loesen.

    Andererseits kannst du dann deinen Ringpuffer nicht dort verwenden, wo du auch ein Array verwenden könntest. Wenn die Implementation aber so schön ist, dass du überall dort, wo du ein Array hast, auch einen Ringpuffer verwenden könntest, dann spricht das deutlich für Vererbung.

    Das hängt davon ab, wie das Array definiert ist. Weder C-Arrays noch std::array oder std::vector sind geeignet, davon abzuleiten, geschweige denn dass sie ein (runtime-)polymorphes Interface anbieten. Von diesen Arrays würde ich also keinen Rungpuffer ableiten.



  • volkard schrieb:

    Firefighter schrieb:

    mich wundert es halt das es dennoch so oft in vielen Clean-Code Buechern vorkommt

    Tja, die Clean-Code-Bewegung halte ich für eine Pest. 😉
    Sie ruft geradezu danach, mit religiösen Inhalten gefüllt zu werden.

    Ich wuerde jetzt nicht gleich "Pest" sagen.
    Einige Ansatzpunkte sind schon recht Wichtig die sie aufzaehlen, aber genau das FCoI ist eines wo ich mir nicht sicher bin ob das wirklich einen Mehr-Nutzen bringt.



  • Personen, Studenten, Beschaeftigte, Professoren

    Klingt klar nach Vererbung.
    Allerdings musst du aufpassen:

    class Hiwi : public Student, public Beschaeftigter
    {
      ...
    };
    

    und Student und Beschaeftigte erben jeweils von Personen -> Diamanten-Problem

    Für diesen Fall müsstest du dann evtl. über virtuelle Vererbung nachdenken.



  • Tachyon schrieb:

    Das ist wieder von der Sprache abhängig. In C++ würde ich das über freie Funktionen und Templates machen. Siehe boost.circular_buffer der sich wie alle anderen Container verhält.

    Und ob da sgeht, ist wieder Situationsabhängig:

    class MyClass{
    
    template<class Array>
    virtual void foo(Array const&);
    };
    

    oops.

    @pumuckl Das ist klar, dass das mit std::vector nicht (unbedingt) funktioniert. Aber guter Punkt.

    @Q Der Fall ist doch DAS Argument für FCoI.



  • otze schrieb:

    class MyClass{
    
    template<class Array>
    virtual void foo(Array const&);
    };
    

    oops.

    Häh 😕



  • virtuelle templates gehen nicht.



  • otze schrieb:

    virtuelle templates gehen nicht.

    Aber was hat das mit dem zu tun, was ich sagte?


Anmelden zum Antworten