"to MI or not to MI"



  • Shade Of Mine schrieb:

    Vererbung will man eigentlich nie haben. Sie ist furchtbar.

    Das kann ich nicht nachvollziehen, konkretes Beispiel: Im DOM gibt es verschiedene Nodes, da will ich schon haben, dass Element von Node ableitet und nicht dauern mit node.type-switch-Triaden rumeiern.

    Und in diesem Zusammenhang hab ich auch ein gutes Beispiel für MI:
    Node - ChildNode (hat einen parent) - ParentNode (hat eine Liste von children) und dann gibt es eben Nodes wie Element die sowohl ChildNode als auch ParentNode sind.

    Das Diamond-Problem ist in C++ zwar etwas hässlich, aber andere Sprachen kommen damit wesentlich besser klar - Eiffel bspw.

    Ich hab mit Interesse den StackOverflow-Artikel aus dem anderen Thread gelesen der das Problem des physikalischen Speicherlayouts behandelt, das ist zwar ein Punkt, aber MI soll ja auch nicht oft vorkommen, aber immer dann wenn es notwendig ist fehlt sie mir doch.

    Jetzt ist es so, dass ParentNode von ChildNode ableitet um Code-Duplikation zu verhindern, was ziemlich, ziemlich übel ist imho. Vor allem weil nicht jeder ParentNode auch ein ChildNode ist und in manchen Ableitungen überprüft werden muss ob man eh keinen Parent hat, etc. etc.

    Welche Alternativen habe ich in dem Fall? Wie kann ich die Klassen besser darstellen und möglichst Code-Duplikation verhindern, eine sinnvolle Klassenhierarchie haben (kein ParentNode : ChildNode) und trotzdem alles auch als Node verwenden zu können?

    Dinge die ich in objektorientierten Sprachen verbieten würde: protected Member.

    @Shade: Mixins sind sehr interessant, in unserer Firma werden die im C#-Bereich sehr viel eingesetzt und sie sind sehr praktisch. Aber wie du schon gesagt hast, da muss sich imho noch etwas tun bis es da den "the way to go"-WEg gibt.

    MfG SideWinder



  • Shade Of Mine schrieb:

    Nein. Schreibarbeit sparen soll dir deine IDE, nicht Vererbung.

    Unabhängig von der Vererbung sehe ich das nicht so. Ich will mich minimal ausdrücken können, es ist schön und gut, dass mir Eclipse die Getter/Setter in Java erstellt, aber ich hasse Java trotzdem dafür, dass ich mir damit meine Klasse zumüllen muss. Die Lesbarkeit verschlechtert sich furchtbar. Ich will C#-Properties.

    C# ist deswegen eine so viel bessere Sprache (Achtung: Sprache, nicht unbedingt Plattform), weil ich mich wesentlich besser ausdrücken kann.

    MfG SideWinder



  • SideWinder schrieb:

    Unabhängig von der Vererbung sehe ich das nicht so.

    Du wuerdest doch nie auf die idee kommen properties fuer normale Funktionen zu verwenden, weil es weniger Schreibarbeit ist, oder?

    class Foo {
      public void writeFunction(string text) { System.Console.WriteLine(text);
      //vs
      public string writeProperty { set { System.Console.WriteLine(value) } }
    }
    

    Selbes bei Sachen wie Vererbung: vererbung drueckt eine Beziehung aus. Wenn man diese Beziehung will, nimmt man vererbung. Will man sie nicht, nimmt man keine Vererbung.



  • Ich verwende Vererbung nicht deswegen um mir Schreibarbeit zu sparen. Aber ich finde nicht, dass die IDE für das Ersparen von Schreibarbeit zuständig ist sondern in erster Linie die Sprache mit ihrer Ausdruckskraft.

    MfG SideWinder



  • SideWinder schrieb:

    Ich verwende Vererbung nicht deswegen um mir Schreibarbeit zu sparen. Aber ich finde nicht, dass die IDE für das Ersparen von Schreibarbeit zuständig ist sondern in erster Linie die Sprache mit ihrer Ausdruckskraft.

    Ich weiss nicht ob ich das Unterschreiben wuerde.
    Klar ist es praktisch wenn ich mit wenigen Zeichen komplexe Sachen ausdruecken kann - aber worum es geht, ist das was ich Ausdruecke.

    Oft will ich ja viel Schreiben, zB bei Funktionsnamen.

    Schreibarbeit sollte IMHO nie ein Argument sein. zB so Sachen wie Access Specifiers wie private/public sind mehr Schreibarbeit als alles public zu machen.

    Auch sind manche Sprachen generell geschwaetziger als andere.

    Wenn also ein Konzept wie Properties Schreibarbeit erspart ist das nett - aber es sollte nie die Motivation fuer die Verwendung so eines Konzeptes sein.



  • Mhm, ich bin nicht in der Lage mich richtig auszudrücken. Ich meine natürlich mit "Schreibarbeit sparen" nicht Variablennamen i,j,k,l,m,n, etc., die darf mir natürlich die IDE mit CTRL+SPACE verlängern.

    Es geht mir in erster Linie um Boilerplate-Code der meine Klasse vergrößert, verkomplexisiert. Eben 10 Getter statt Properties. Eine 5-Zeilen-For-Schleife die Elemente filtert anstatt result = collection.where(.Value > 10). Solcherlei.

    Die For-Schleife muss sich jemand erst ansehen, nachsehen ob sie eh kein +=2 im Index hat, ob sie eh nicht irgendwo etwas ein klein wenig anders macht. Weil ich nicht ausdrücken kann was ich eigentlich sagen mag: Gib mir nur jene Elemente deren Value größer als 10 ist.

    Edit: Darum geht's hier aber nicht. Würde mich eher freuen wenn jemand etwas zu meinem Posting über MI schreibt 🤡

    MfG SideWinder



  • Kellerautomat schrieb:

    Da mich das Thema interessiert, moechte ich hier gerne die MI-Diskussion aus diesem Thread fortsetzen: http://www.c-plusplus.net/forum/314127

    Einerseitz finde ich Xins Ansatz interessant, rein logisch kann ich dem jedoch (noch) nichts abgewinnen. Darum moechte ich Xin bitten, ein nicht konstruiertes Modell aus realem Code zu zeigen und zu erklaeren.

    Sorry, für dieses eher sinnlose Posting, ich habe den Thread hier gerade erst nach einem Hinweis von Sidewinder gesehen. Durch RudP cruise ich nur bei zuviel Zeit und ich kann jetzt nicht drei Seiten durchhangeln...

    Kellerautomat schrieb:

    Ich bitte darum, die Diskussion konstruktiv fortzusetzen und nicht wieder gegenseitig aufeinander rumzuhacken - das fuehrt zu nichts.

    ...mag mich aber auch keiner Diskussion entziehen, die offen und konstruktiv werden soll. Ich hoffe heute abend nochmal Zeit zu finden und klinke mich dann nochmal ein.



  • SideWinder schrieb:

    [...]

    Und in diesem Zusammenhang hab ich auch ein gutes Beispiel für MI:
    Node - ChildNode (hat einen parent) - ParentNode (hat eine Liste von children) und dann gibt es eben Nodes wie Element die sowohl ChildNode als auch ParentNode sind.

    Das Diamond-Problem ist in C++ zwar etwas hässlich, aber andere Sprachen kommen damit wesentlich besser klar - Eiffel bspw.
    [...]

    MfG SideWinder

    Was genau hat man denn dann eigentlich gewonnen? Nun sind die Objekte zwar in sich klar strukturiert, aber der Code, welcher ein fertiges DOM vorgesetzt bekommt (und das ist ja der einzig relevante Code, für den man die Mühe betreibt, alles klar, leserlich und strukturiert zu machen, als Bibliotheksschreiber), weiß doch immer noch nicht die konkreten Typen und muss trotzdem irgendwie Type-Switching betreiben. Oder sehe ich da was falsch?

    Auf meiner Todo-Liste steht auch bald wieder eine einfache Baumstruktur und ich habe leider noch keine gute Lösung für das Inner-Node/Leaf-Problem gefunden. Sollte jeder innere Knoten eventuell zwei getrennte Listen für weitere untergeordnete innere Knoten bzw. Blatt-Knoten enthalten? Sollte er sie in einer Liste vorhalten und als Implementierungsdetail den Type-Switch für den Client-Code durchführen?

    Edit: Bliebe natürlich noch, die Typ-Auflösung per Double-Dispatch zu betreiben. Irgendwie habe ich das aber noch nicht zu schätzen gelernt, weiß auch nicht warum.



  • @Decimad: Hmm, ich bin einfach schlecht im Ausdrücken heute, aber anders gesagt: Wenn ich keine gemeinsame Basisklasse habe, kann ich im ParentNode nicht einmal eine List<ChildNode> halten die mir auch gleich autom. garantiert, dass ich nicht einen Non-ChildNode Child als einhängen kann.

    MfG SideWinder



  • SideWinder schrieb:

    @Decimad: Hmm, ich bin einfach schlecht im Ausdrücken heute, aber anders gesagt: Wenn ich keine gemeinsame Basisklasse habe, kann ich im ParentNode nicht einmal eine List<ChildNode> halten die mir auch gleich autom. garantiert, dass ich nicht einen Non-ChildNode Child als einhängen kann.

    Das Problem dabei ist aber, dass ChildNode kein gute Klasse ist. Du willst eine List<Node> haben. Ob es einen parent hat oder nicht, ist doch eigentlich irrelevant.

    Warum genau muss ein Leaf Node anders behandelt werden als ein Non-Leaf Node?

    Was den Boilerplate Code betrifft: klar ist es toll wenn man wenig Boilerplate hat - aber das ist kein Argument fuer ein Konzept. Ich verstehe schon was du meinst - aber du musst mit dieser Argumentation aufpassen, da es oft irrelevant ist wieviel Schreibarbeit man hat wenn die Modellierung stimmt. Und darum geht es: das Model muss passen. Wenn ich mit 3 Zeilen ein komplexes Modell bauen kann ist das super. Aber man sollte lieber das korrekte modell in 20 Zeilen schreiben als ein falsches Modell in 2 Zeilen.

    Es ist ein praktisches Feature einer Sprache, wenn es viele Modelle oder Ausdruecke direkt per wenigen Keywords kann - aber man darf NIE NIE NIE falsch modellieren nur weil die falsche modellierung weniger Schreibarbeit ist.



  • Nun, von müssen kann natürlich keine Rede sein, aber in meinem Fall unterscheiden sich Blattknoten von den inneren Knoten nicht alleine durch die Abwesehnheit von Kindknoten.
    Geht hier einfach um einen Baum von Liniensegmenten, bei denen der Enpunkt eines Liniensegments gleichzeitig der Startpunkt der Kind-Segmente ist, wobei das letzte Liniensegment aber nicht auffächert, sondern an etwas anderes andockt. Geht hier um graphische Repräsentation von Kanten-Bäumen in einem Netzwerk.
    So stelle ich mir das zumindest gerade alles vor.



  • Ich kann vielleicht mit einer List<Node> noch leben, und dank Interfaces habe ich zumindest in diesem Bereich (Collection-Haltung von Objekten) keine Probleme in den gängigen SI-only-Sprachen.

    Trotzdem: Element ist ein ParentNode und ist ein ChildNode. Das ist das korrekte Modell. Ein ParentNode hat eine List<Node/ChildNode> als Kinder, ein ChildNode hat einen Up-Pointer.

    Es gibt reine ParentNodes im DOM, es gibt reine ChildNodes und es gibt Node-Typen die sind nunmal beides.

    Mag sein, dass der DOM-Standrad Käse ist was OO betrifft, aber das hilft mir bei der Implementierung desselbigen auch wenig.

    Die meisten Implementierungen (bspw. Apache Xerces) leiten nun ParentNode von ChildNode ab und leben mit einem Null-Up-Pointer und anderen Kleinigkeiten die ChildNodes haben und ParentNodes nicht, aber letzten Endes ist das Modell falsch umgesetzt, und genau das bekreidest du ja ausdrücklich, nur, dass ich bspw. in Java keine Möglichkeit habe das korrekt auszudrücken - nicht einmal sher umständlich und hässlich.

    MfG SideWinder



  • Ne, das Modell ist nicht falsch umgesetzt. Die Trennung Leaf/NonLeaf gibt es nicht. Wenn ich an einen Leaf Knoten einen Knoten dranhaenge ist er nicht mehr Leaf.

    Man kann auch einfach uebermodellieren. Welche Probleme siehst du, wenn es nur eine Knotenklasse gibt die einen optionalen Parent Zeiger hat und eine optionale Liste an Kind-Knoten?

    Welche Probleme entstehen, wenn es so modelliert ist?

    Gerade bei DOM Knoten hat jeder Knoten immer potentielle Kinder und bis auf root jeder Knoten einen Parent.

    Wenn du zB den Parent-Zeiger einsparen willst, aus Platz/Performance Gruenden, dann kostet dich die vtable ploetzlich wieder mehr Platz und Performance als die Einsparung. Auch ist die Frage wie praktisch sollen die Algorithmen auf dem Baum laufen? Will ich beim traverse jedesmal dynamic casts haben um zu checken ob der Knoten Kinder hat?

    Man muss beim modellieren etwas aufpassen dass man nicht das wesentiliche aus den Augen verliert. ein String ist zB einfach ein char-array und keine abstrakte Collection bestehend aus lauter abstrakten Character-Klassen.



  • Leider kennst du dich mit den Eigenheiten von DOM nicht gut genug aus, wenn du glaubst, dass jeder Knoten potentiell Kindknoten haben kann und nur der Root-Knoten keinen Vater hat. Dann ist es natürlich jetzt schwer für dich zu modellieren. Es gibt Knoten im DOM (nein, nicht "Element", ein Element ist nur ein Typ von Knoten) die keine Kind-Knoten haben können, auch nicht potentiell.

    Ja, die DOM-API bietet trotzdem Node.getChildNodes() welches null liefert für alle non-ParentNodes bzw. nur in ParentNode überladen wird. Man könnte natürlich hier wie gesagt schon den DOM-Standard ankreiden, aber wenn man ihn implementieren will dann wohl mit dieser Vererbung und am schönsten mit MI - mal ganz abgesehen von Performance.

    MfG SideWinder



  • SideWinder schrieb:

    Leider kennst du dich mit den Eigenheiten von DOM nicht gut genug aus, wenn du glaubst, dass jeder Knoten potentiell Kindknoten haben kann und nur der Root-Knoten keinen Vater hat. Dann ist es natürlich jetzt schwer für dich zu modellieren. Es gibt Knoten im DOM (nein, nicht "Element", ein Element ist nur ein Typ von Knoten) die keine Kind-Knoten haben können, auch nicht potentiell.

    Stimmt. An Text Nodes habe ich nicht gedacht.

    Ja, die DOM-API bietet trotzdem Node.getChildNodes() welches null liefert für alle non-ParentNodes bzw. nur in ParentNode überladen wird.

    Korrekt waere eine leere Liste und nicht null.

    Aber ja, nicht jedes bestehende Design ist perfekt. Ich parse DOM eigentlich nur mit JavaScript(jQuery) und da ist es halt easy - ich kann immer node.children() callen, egal was es fuer eine Node ist. Wenn es eine Textnode ist, dann bekomme ich eben eine leere Liste. (wobei man mit jQuery eigentlich keine Textnodes ansprechen kann, da eine Textnode eigentlich teil des Parents ist)

    Man könnte natürlich hier wie gesagt schon den DOM-Standard ankreiden, aber wenn man ihn implementieren will dann wohl mit dieser Vererbung und am schönsten mit MI - mal ganz abgesehen von Performance.

    Sehe ich, wie gesagt anders. Eine Spezifikation ist eben das: eine Text der etwas beschreibt. Wie ich das modelliere ist meine Sache.

    jQuery hat zB ein sehr schoenes DOM Interface. JavaScript ist hier natuerlich auch viel besser geeignet als C++, keine Frage. Aber nur weil in einer Spezifikation von A und B die Rede ist, muss man dies noch lange nicht als 2 unterschiedliche Klassen modellieren.

    PS:
    Und wenn man performance will, darf man hier sowieso nicht vererben.

    Aber erklaer mal genau welches Problem du loest, wenn du mehrere Node Typen hast.



  • Xin schrieb:

    ...mag mich aber auch keiner Diskussion entziehen, die offen und konstruktiv werden soll. Ich hoffe heute abend nochmal Zeit zu finden und klinke mich dann nochmal ein.

    😞



  • So wie ich Xin verstanden habe, wandelt er

    struct Mensch {
      Kopf kopf;
      Bein links, Bein rechts;
    };
    

    in

    struct LinkesBein : Bein {};
    struct RechtesBein : Bein {};
    struct Mensch : Kopf, LinkesBein, RechtesBein {};
    

    um.

    Sein Argument war, dass er dann statt mensch.kopf.nicke() einfach mensch.nicke() schreiben kann, im Falle des Diamantproblems halt mensch.LinkesBein::schreite() .

    Aus klassischer OOP-Sicht ist das natürlich kreuzfalsch und das kann man ihm mit dem klassischen OOP-Vokabular problemlos beweisen. Deshalb sollte man hier auch nicht von Vererbung sprechen.

    Allerdings sehe ich noch nicht ganz den Mehrwert, eher die Probleme, die auftreten. Wenn man dem Menschen noch einen weiteren Kopf hinzufügt, kompiliert Code nicht mehr, der früher problemlos ging; was heisst, dass hier die Kapselung sehr niedrig ist.



  • Thingy schrieb:

    So wie ich Xin verstanden habe, wandelt er

    struct Mensch {
      Kopf kopf;
      Bein links, Bein rechts;
    };
    

    in

    struct LinkesBein : Bein {};
    struct RechtesBein : Bein {};
    struct Mensch : Kopf, LinkesBein, RechtesBein {};
    

    um.

    Sein Argument war, dass er dann statt mensch.kopf.nicke() einfach mensch.nicke() schreiben kann, im Falle des Diamantproblems halt mensch.LinkesBein::schreite() .

    Aus klassischer OOP-Sicht ist das natürlich kreuzfalsch und das kann man ihm mit dem klassischen OOP-Vokabular problemlos beweisen. Deshalb sollte man hier auch nicht von Vererbung sprechen.

    Das wäre auch kreuzgefährlich.
    Jemand hat vergessen zu schreiben

    Mensch::umdrehen(int anzahlViertelDeheungenNachRechts){
       for(int i=0;i<anzahlViertelDeheungenNachRechts;++i){
          RechtsBein::schrittZurück();
          LinkesBein::SchrittVor();
          Hüfte::ausrichten();
          RechtsBein::halbRanziehen();
          LinkesBein::ranziehen();
       }
    }
    

    Kommt ja mal vor, daß man was vergessen hat.
    Es folgt

    Mensch::onHoereHupeVonHinten(){
       umdrehen(4);//STILLSCHWEIGEND GEERBT VON KOPF!!!!!!
       ...
    }
    

    Und zack, Kopf umgedreht, Genickbruch, tot.
    Das ist nicht optimal.

    Es ist nicht gut, statt statt mensch.kopf.nicke() einfach mensch.nicke() schreiben können zu wollen.
    🤡



  • Kellerautomat schrieb:

    Xin schrieb:

    ...mag mich aber auch keiner Diskussion entziehen, die offen und konstruktiv werden soll. Ich hoffe heute abend nochmal Zeit zu finden und klinke mich dann nochmal ein.

    😞

    Sorry. Ich bin an dem Abend nicht mehr zu Hause angekommen, der Freitag und Samstag waren vorher schon ausgebucht und dann kommt erstmal mein eigenes Forum und jede Menge anderer Kram und "schon" bin ich hier und darf mich erstmal einlesen. 🙂

    Notfalls PM - ich kann ja auch mal was vergessen.

    Ich weiß auch nicht, ob ich jetzt auf alles eingehe - das Posting wird vermutlich eh elendig lang.

    Trotzdem eine kurze allgemeine Einleitung: Kellerautomat bemerkte ja bereits, dass viele Ideen über "Vererbung" ausgedrückt werden. Wenn also "vererbt" wird, dann heißt das nicht zwangsläufig, dass hier irgendwas etwas an eine nachfolgende Generation von Datentypen übergeben wird.

    Punkte, die Biegungen beschreiben, erben von einfachen Punkt. Alles geht als Member, aber ein Biegepunkt ist auch ein Punkt. Hier wird etwas vererbt. Das ganze dient dazu, Objekte zu klassifizieren: Eben Klassen zu bauen. Der Biegepunkt gehört zur Klasse der Punkte.

    Über virtuelle Funktionen kann man nun erlauben, Methoden auf den spezielleren Typ anzupassen. Ab hier befinden wir uns in der Objektorientierten Programmierung, bzw. besser gesagt in der "Datentyporientierten Programmierung", weil das Objekt ist egal, die Funktion wird anhand ja des Datentyps des Objekts bestimmt, aber der Spaß ist als OOP bekannt.
    Vorher haben wir einfach nur klassifiziert. Vererbung drückt Subklassifizierungen aus.

    "Erbt" man von einem Interface, so hat das eigentlich nichts mit "man bekommt etwas" zu tun, sondern man muss etwas tun: nämlich die Pure Virtual Methods implementieren. Hier wird über die "Vererbung" eher eine Vertrag ausgedrückt, ähnlich wie mit Referenzen der Vertrag zustande kommt, dass das referenzierte Objekt nicht Null ist oder bei const der Vertrag vermittelt wird, dass das Objekt eben nicht verändert wird.

    class VarDecl : public SubTypeableOperator
                  , public XSD::String::VirtualConstIdentifierInterface
    

    Die Syntax ist ähnlich, aber es passiert eigentlich etwas vollkommen anderes. Ich behaupte, dass eine Variablendeklaration ein SubtypeableOperator ist und gehe gleichzeitig den Vertrag ein, dass ich diese Klasse nach einem Identifier fragen darf. An der Klassifizierung der Klasse ändert die zusätzliche Möglichkeit eigentlich nichts. Man kann jetzt natürlich sagen, dass VarDecl zur Klasse der Identifierzurückgeber gehört, aber dafür hat sie weder Member noch Fähigkeiten gerbt, ich drücke hier nur aus, dass VarDecl selbst etwas tun muss.

    Wichtig hier ist schonmal, dass wir bisher zwei unterschiedliche Ideen, zwei unterschiedliche Ansprüche mit dem Wort "Vererbung" bezeichnen. Diese zwei sind derzeit üblicherweise anerkannt.

    Kellerautomat schrieb:

    Einerseitz finde ich Xins Ansatz interessant, rein logisch kann ich dem jedoch (noch) nichts abgewinnen. Darum moechte ich Xin bitten, ein nicht konstruiertes Modell aus realem Code zu zeigen und zu erklaeren. Beispiele tendieren dazu, als Diskussionsgrundlage ungeeignet zu sein. Vielleicht lerne ich ja etwas dazu - bisher habe ich MI kaum verwendet.

    Ich schreibe ja eine Programmiersprache. Nun gibt es in Programmiersprachen Beispielsweise Klammern - "Scopes". Innerhalb dieser Scopes darf man neue Typen definieren, so darf man innerhalb eines Scopes eine neue struct definieren, aber eben

    int main() 
    { 
      {
        struct Point { int x; int y; }; 
      }
    
      // Datentyp Point unbekannt.
    }
    

    Diese Scopes sind Operatoren, die eigene Typdefinitionen enthalten dürfen. Die Fähigkeit eine Liste von Datentypen zu zu kennen heißt bei mir SubTypeable und ein Operator ist halt ein Operator. "Scopes" erbt also von der Klasse "SubtypeableOperator", weil er als Operator in den Abstract Syntax Tree eingeht und man eben Typen hinzufügen kann.

    class SubTypeableOperator : public Operator
                              , public SubTypeable
    

    Das dürfte etwa das sein, wobei einige hier Probleme sehen. Warum auch immer. Da sich weder Operator noch Subtypeable in die Quere kommen, besteht für mich überhaupt kein Grund, Subtypeable als Member zu nehmen. Zumal hier die Frage nach der Klassifizierung zu bejahen ist: Ein SubTypeable-Opertor ist fähig Typdefinitionen aufzunehmen und es ist ein Operator. Es werden auch nicht nur "Verträge" geschlossen, sondern es werden tatsächlich Fähigkeiten vererbt.

    Nächster Schritt: Ein Quelltext ist bei mir ein Operator.
    Klingt erstmal merkwürdig, ist in meinem Sprachdesign aber so gewünscht.

    class SourceOperator : public Source
                         , public SubTypeableOperator
    

    Ein Quelltext wird erstmal geladen. Die Quelle hat also gewisse Grundeigenschaften: Zum Beispiel einen Namen:

    class Source : public DoubleCodePosition
                 , public XSD::Type::CNamedNode<Source>
                 , public OpPrio
    

    Bei DoubleCodePosition werden einige wieder auf die Barrikaden gehen, das könnte man auch als Member bezeichnen, aber wenn ich eine Fehlermeldung ausgebe, dann will ich einfach nur die Codeposition wissen und werfe die Variable, die den Quelltext repräsentiert direkt in den Logger. Für mich praktisch - und auch nachvollziehbar, denn in dem Quelltext ist ein Fehler, und der Logger holt sich direkt die Stelle im Code raus, wo ich den Fehler entdeckt habe. Warum soll ich mich darum kümmern?

    Was ist das jetzt für eine Beziehung? Es wird etwas geerbt, aber es ist keine "ist-ein" Beziehung. Es ist der Vertrag, dass man das Objekt fragen kann, wo es gerade dran ist. Würde ich ein CodePositionInterface vererben, das von mir verlangt, ein GetCurrentCodePosition() und ein SetCurrentCodePosition() zu implementieren und damit den entsprechenden Member zu bearbeiten, wäre das allseits gute und anerkannte Programmierung. Jetzt spare ich mir den Blödsinn und leite direkt davon ab, muss keine pure virtual Funktionen implementieren und keine Member erzeugen, um die Funktionen richtig bedienen zu können, ich muss mich um gar nichts kümmern, nach der Ableitung funktioniert das schon alles.
    Das ist das hierzulande falsch, obwohl hier nichts anderes passiert - wie ich vorher schon sagte: Aggregation, Komposition, Mehrfachvererbung... am Schluss kommt das gleiche raus, die Frage ist, welchen Aufwand man selbst damit hat.
    Die Frage mit den leeren Strukturen hat sich mir noch nie bei Vererbung gestellt, außer als Marker bei Templates brauchte ich noch nie leere Strukturen.

    Source selbst erbt weiterhin einen Namen - in der Regel den Dateinamen - und die Fähigkeit Operatorprioritäten zu bestimmen. Das Zeug brauche ich, um Quelltext zu lesen.

    Weiterhin ist ein SourceOperator ein SubTypeableOperator. Während ich also den Quelltext lese, baue ich die entsprechenden Dateitypen und Funktionen auf und lege sie im SourceOperator ab, der Teil des AST wird. Ein Sourceoperator ist ein Subtypeable-Operator, der fähig ist, einen AST aufzubauen.

    In C++ kommt das am ehesten #include.
    Hier werden Fähigkeiten zusammengesetzt. Von Operator "erbt" man eine virtuelle Funktion in der der SourceOperator beschreiben muss, was während des Parsens zu tun gedenkt. Das ist der Klebstoff, der Source und Subtypeable-Operator zusammenfügt.

    knivil schrieb:

    Erklaer mal warum Vererbung anstatt Aggregation benutzt werden sollte.

    Interessanter Anspruch. Wenn man sich in der Geschichte der Programmiersprachen so umschaut, ist die Formulierung mit Membern älter als die Vererbung. Strukturen/Records gibt es schon recht lange, Vererbung hingegen erst später.
    Üblicherweise lernt man Strukturen auch deutlich vor Vererbung, was schon alleine deswegen logisch und notwendig ist, weil man bekanntlich nicht von Primitiven ableiten kann. Das ist schon ein guter Grund, warum bei Primitiven keine Ableitung verwendet werden sollte.

    Nehmen wir eine Adresse:

    class Address
    {
      protected :
        unsigned int id;
      public:
        string       street;
        unsigned int housenumber;
        unsigned int zipcode;
        string       town;
    };
    
    printZipCopde( unsigned int zip );
    

    In die Funktion printZipCode kann ich alles reinwerfen, was sich als unsigned int verkaufen lässt, zum Beispiel die Hausnummer. Würde ich aber die Struktur mit Ableitungen versehen, habe ich die passenden Typen und kann die auch verlangen:

    class Address
      : protected Id
      , public    Street
      , public    HouseNumber
      , public    ZipCode
      , public    Town
    {};
    
    printZipCopde( ZipCode zip );
    

    Ich habe mal eine protected Id eingefügt, damit man sieht, dass wir hier auch nicht an der Sichtbarkeit scheitern.

    Für die Ableitung muss ich Typen generieren. Auf Deine Frage, warum man Vererbung nutzen sollte, kann ich Dir keine Antwort geben, da ich ja behaupte, das prinzipiell eh das gleiche rauskommt. Schau Dir OOP in C an. Da wird über Member vererbt, weil man keine Ableitungen formulieren kann.
    Ich kann Dich aber fragen, warum man eigentlich Membervariablen verwenden sollte.

    Man muss mal ganz klar sehen und auch sagen, dass ich mich gedanklich nicht in den Grenzen von C++ bewege, aber hier mit C++ Gedanken formuliere. Ich bin aber nicht auf die Syntax von C++ angewiesen.
    Ich arbeite hart daran, aus den Grenzen auszubrechen, die ich genauso selbstverständlich sehe, wie jeder, der ein paar Jahre programmiert. Man kann nicht von int ableiten. Warum nicht? Ich habe gedanklich überhaupt keine Skrupel von int abzuleiten, ich kann es in C++ nur nicht ausdrücken. Ich muss erst eine struct Integer mit einem int als Member bauen, dann geht's.

    struct ZipCode
      : public Integer
    {};
    

    Das Problem ist also nicht, dass es nicht machbar wäre, sondern das es in C++ nicht so einfach formulierbar ist. C++ ist dafür nicht optimal ausgelegt, was ich ganz klar darauf schiebe, dass C++ eine Weiterentwicklung von C ist, entsprechend aufgebaut auf die Probleme und Möglichkeiten, die man zu der Zeit hatte. Vererbung und auch OOP war in C nicht unbekannt, aber eben bei weitem nicht so üblich, wie es das heute ist. Also hat man erstmal einfach genauso auf wieder die Member gesetzt, wie zuvor in C, in Algol oder Fortran und die Vererbung hinzugefügt.

    Shade Of Mine schrieb:

    Denn alles was dir eine Ableitung bringt, ist die schreibarbeit fuer data() und op[] zu ersparen.

    Da hast Du in C++ grundsätzlich recht. Aber erstens ist das Einsparen von Schreibarbeit in meinen Augen besser als Schreibarbeit und zum anderen spart es auch "Lesarbeit", da die Memberbeschreibung, sofern sie eindeutig ist, fehlt, wie oben beim Logger: Ich will nicht wissen, dass der Logger die aktuelle Zeilennummer des Quelltextes braucht, mir reicht es die Variable source reinzupacken, statt die Zeile mit source.getCodePosition() voll zu müllen und die zu loggende Meldung rauszuschieben.

    Shade Of Mine schrieb:

    Schreibarbeit sparen soll dir deine IDE, nicht Vererbung.

    Ich will mich möglichst kurz ausdrücken können und ich will möglichst wenig lesen müssen. Und ich möchte möglichst auf Membernamen verzichten, weil sich Fähigkeiten nicht in den Membernamen widerspiegeln, sondern in den Typen und deren Aufbau.

    SideWinder schrieb:

    Und in diesem Zusammenhang hab ich auch ein gutes Beispiel für MI:
    Node - ChildNode (hat einen parent) - ParentNode (hat eine Liste von children) und dann gibt es eben Nodes wie Element die sowohl ChildNode als auch ParentNode sind.

    Gefällt mir. Hier nutzt Du die Vererbung, um Fähigkeiten zu kombinieren, nämlich Kinder zu haben, bzw. Kind zu sein. Packst Du noch einen Marker DOMElement dazu, das Wurzel, Knoten und Blatt markiert, kannst Du Funktionen schreiben, die entweder nur Wurzeln, Knoten oder Blätter, Kinder habende, Kinder seiende oder eben DomElemente akzeptiert.

    Damit wären wir dann auch bei einer leeren Struktur "DomElement", die hier als "Marker" genutzt würde, was man gewissermaßen als vierte Idee beschreiben muss, für die "Vererbung" in C++ missbraucht wird, bzw. ein leeres Interface in Java/C#. Eine Instanz von DomElement würde allerdings keinen Sinn ergeben.

    SideWinder, über die Marker habe ich noch nicht als eigenständiges Konzept nachgedacht, aber für mich hat sich dieses Posting hiermit schon gelohnt. 🙂

    Shade Of Mine schrieb:

    Ob es einen parent hat oder nicht, ist doch eigentlich irrelevant. Warum genau muss ein Leaf Node anders behandelt werden als ein Non-Leaf Node?

    Ein konkretes Beispiel hat immer den Nachteil, dass es auch auf eine bestimmte Art anders zu lösen ist oder in dem Fall stellst Du Dir die Frage, ob das bei genau diesem Beispiel für Dich Sinn ergibt.

    Für andere kann der Unterschied aber durchaus relevant sein. Wenn Du eine Funktion schreibst, die nur Blätter akzeptiert, kannst Du das im Datentyp ausdrücken und musst dann keine Fragen stellen. Er muss auch nicht Fehlercodes oder Exceptions bearbeiten, die "falscher Nodetyp" bedeuten, weil er gar keine falschen Nodes übergeben kann.
    Weiß der Rufende, dass er nur Blätter hat, kann er das direkt zu Dir durchleiten und es werden niemals Fragen gestellt: Weniger Fragen => schnellerer Code.

    Shade Of Mine schrieb:

    Die Trennung Leaf/NonLeaf gibt es nicht. Wenn ich an einen Leaf Knoten einen Knoten dranhaenge ist er nicht mehr Leaf.

    Gerade im DOM gibt es sie.
    <h1>Hallo Welt</h1>
    Du kannst an das Blatt "Hallo Welt" keine Kinder anfügen.

    Shade Of Mine schrieb:

    Man kann auch einfach uebermodellieren.

    Das wiederum glaube ich nicht.

    Die Informationsmenge wächst und wird detaillierter. Die Information, dass es Leafs geben könnte, die niemals Kinder haben, ist Dir zuerst selbstverständlich vorgekommen, aber hättest Du es modelliert, hättest Du bei den String eben auch gemerkt, dass Du quasi untermodelliert hast.

    Ich glaube, man kann sich in einer Unmenge von Klassen verlieren, aber je genauer ein Typ beschreibt, was er kann oder eben nicht, desto besser. Die Frage, die man stellen darf und die auch zu stellen ist, ist ob C++ Vererbung ausreichend unterstützt, als dass man es uneingeschränkt empfehlen dürfte. Da wir an der Ableitung von int schon scheitern, geht uneingeschränkt ja schon gar nicht.

    Die Mehrfachvererbung "tagt" gewissermaßen Daten statisch, das bedeutet, das Tag kostet keine Rechenleistung, man kann mit falsch getagten Daten keinen Unsinn machen. Mit diesen Tags kann man aber Rechenleistung sparen, weil man eben keinen Member fragen muss, ob der "Tag" besteht oder nicht.

    In C++ besteht allerdings das Risiko, dass man einfach sehr viel Code produziert, um C++ die einzelnen Tags bekannt zu machen.

    Die Tags sind das große Potential von Mehrfachvererbung.

    Thingy schrieb:

    Allerdings sehe ich noch nicht ganz den Mehrwert, eher die Probleme, die auftreten. Wenn man dem Menschen noch einen weiteren Kopf hinzufügt, kompiliert Code nicht mehr, der früher problemlos ging; was heisst, dass hier die Kapselung sehr niedrig ist.

    Geniales Beispiel! Wenn ein Mensch zwei Köpfe hat, finde ich es super, wenn der Code nicht kompiliert. Das klingt nämlich nicht gesund.

    Der Mensch hat - wie Du richtig beschrieben hast - zwei Beine, aber es sind nicht zwei identische Beine. Sie befinden sich ja auch nicht am selben Ort: Das eine Bein ist links, das andere rechts. Und das beschreibst Du auch statisch über die beiden Strukturen LinkesBein und RechtesBein. Ein Mensch kann auch keine zwei identischen Köpfe haben. Wenn Du also einen zweiten Kopf willst, nötigt Dich der Compiler den Unterschied auch zu beschreiben und bietet Dir danach die Möglichkeit, Funktionen zu schreiben, die entweder nur den einen oder nur den anderen Kopf akzeptieren, so dass Du für wichtige Entscheidungen KopfZwischenSchultern verlangen kannst und nicht nur irgendein Member vom Typ Kopf, wo dann versehentlich das Member DenkOrganZwischenBeinen verwendet wird.
    Das vermeidet Bugs.

    volkards Posting verstehe ich nicht, aber mir scheint, dass es auch nicht zur Eingangsforderung passt:

    Kellerautomat schrieb:

    Ich bitte darum, die Diskussion konstruktiv fortzusetzen und nicht wieder gegenseitig aufeinander rumzuhacken - das fuehrt zu nichts.

    So, hackt los, dank SideWinders Post hat sich die Beantwortung hier für mich schon ausgezahlt, mir kann also nix mehr passieren. 😉



  • Xin schrieb:

    volkards Posting verstehe ich nicht

    Das ist ein ideologisches Problem.
    Lies meins bitte noch mehrmals ganz unvoreingenommen. Es müßte "klick" machen. Bitte, bitte.

    Ich habe mir so eine Mühe gegeben. Das MUSS man doch verstehen. Ich *kann* Dich auch mit Deinen Beispielen zu widerlegen versuchen, aber es dauert wirklich mir jedesmal zehnmal so viel Zeit, eine jedem (außer Dir) verständliche Widerlegung zu bauen, als Dir es dauert, den Müll reinzupusten. Das ist nicht fair. Und wenn Du dann alles ingnorierst, immer und immer wieder, haben wir konstant ein 1:10-Verhältnis. Das ist nicht ok. Sei mir nicht böse, aber da muss eine andere Basis her, um konstruktiv zu bleiben. Denke, eine Woche habe ich noch Geduld. Danach müssen wir aufgeben und zum Beispiel gegenseitig ein Fachbuch empfehlen, das der andere kauft, nebst Seitenangabe.


Anmelden zum Antworten