Problem mit Klassenhierarchie.



  • Schönen guten Tag 🙂
    Wiedermal brauche ich Hilfe.

    Folgendes Problem:

    Ich habe eine abstrakte Mutter-Klasse field. Eine von field abgeleitete Basisklasse street, und weiters noch eine von street abgeleitete Klasse companie.
    In einer von der Hierarchie soweit unabhängige Klasse board habe ich ein Array von Pointern vom Typ field deklariert, welches mit Objekten der Klassen street und companie

    Mein erstes Problem war:
    Da ich immer über field_ptr_[x]-> auf die angelegten Objekte von street oder companie zugreife, muss ich scheinbar JEDE Methode die ich in einer abgeleiteten Klasse verwenden will auch in der Mutterklasse deklarieren (natürlich mit virtual). Soweit hat mich das nur bedingt gestört, denn es funktioniert.

    Da ich nun aber die Klasse companie zum Programm hinzugefügt habe steh ich vor einem Problem. Da diese nichtmehr direkt von der Mutterklasse abgeleitet ist, sondern lediglich von einer Ableitung der Mutterklasse, weiß ich nicht wie ich Methoden der Klasse companie aufrufe.

    Deklariere ich die Methoden von companie in der Mutterklasse _nicht_, so kennt er diese dann nicht. Deklariere ich sie mit Virtual ruft er die methode der Mutterklasse selbst auf.

    Falls mein Problem zu schwer zu verstehen ist versuche ich gerne noch meinen Programmcode zurechtzuschnipseln und in hier zu posten.
    Andernfalls bedank ich mich schonmal recht Herzlich für die Hilfe : )
    lg



  • sust schrieb:

    Da ich immer über field_ptr_[x]-> auf die angelegten Objekte von street oder companie zugreife, muss ich scheinbar JEDE Methode die ich in einer abgeleiteten Klasse verwenden will auch in der Mutterklasse deklarieren (natürlich mit virtual).

    Selbstverständlich, Du kannst aus field_ptr nur Methoden aufrufen, die dort auch bekannt (deklariert) sind.

    sust schrieb:

    Da ich nun aber die Klasse companie zum Programm hinzugefügt habe steh ich vor einem Problem. Da diese nichtmehr direkt von der Mutterklasse abgeleitet ist, sondern lediglich von einer Ableitung der Mutterklasse, weiß ich nicht wie ich Methoden der Klasse companie aufrufe.

    Das ist dasselbe Problem ...

    sust schrieb:

    Deklariere ich die Methoden von companie in der Mutterklasse _nicht_, so kennt er diese dann nicht. Deklariere ich sie mit Virtual ruft er die methode der Mutterklasse selbst auf.

    Wenn Du alles richtig gemacht hast, muß das funktionieren.

    Diese Methoden stehen dann übrigens auch in street zur Verfügung, wo sie offensichtlich aber nix zu suchen haben ...
    Das sieht alles nach einem etwas unglücklichen Design aus ...



  • Es ist egal ob eine klasse direkt oder inidrekt von der "mutterklasse" abgeleitet ist. Wenn du in der Mutterklasse eine Methode als virtuell deklariert hast, dann wird beim Aufruf über einen pointer (oder eine Referenz) immer die entsprechende methode der am weitesten abgeleiteten Klasse aufgerufen.
    Um ehrlich zu sein legt die Art und Weise wie du dein Problem schilderst nahe, dass du den Sinn und die Funktionsweise von Polymorphie und virtuellen Funktionen noch nicht ganz verstanden hast. Du solltest dich nach einem entsprechenden Tutorial/Buch zu dem Thema umschauen.



  • Also, das ganze wird (wohl sehr schwer zu erahnen welches ;P) ein Brettspiel.

    In diesem Spiel gibt es 40 Felder (fields). Nun habe ich eine Mutterklasse field, welche abstrakt ist da ja jedes Feld für sich eine bestimmte art von Feld ist, und ein Objekt von der allgemeinen Klasse field (imho) wohl kaum sinnvoll wäre.
    Es gibt nun erstmal vier verschieden Arten von fields: Straßen, Firmen, Bahnhöfe und Ereignisfelder. All diese sind von der Mutterklasse field abgeleitet, da sie ja alle Felder sind und somit gemeinsamkeiten haben. (Name, Gruppen, Farbe usw. )
    Da Straßen zwar sehr viel Umfangreicher, jedoch sonst den Firmen und Bahnhöfen relativ ähnlich sind ( und somit viele methoden und attribute teilen) möchte ich Firmen und Bahnhöfe von den Straßen ableiten um all die gleichen Methoden und Attribute von Straßen nutzen zu können.
    Ereignisfelder zb. werden lediglich von der Mutterklasse abgeleitet da sie nicht gekauft, bebaut oder vermietet werden können.

    Es kann ja sein dass es eleganter, besser und einfacher machbar ist, aber ist das Design im großen und ganzen nicht ganz in ordnung ?
    Bin für jeden Vorschlag dankbar 🙂



  • Belli schrieb:

    Wenn Du alles richtig gemacht hast, muß das funktionieren.

    Diese Methoden stehen dann übrigens auch in street zur Verfügung, wo sie offensichtlich aber nix zu suchen haben ...
    Das sieht alles nach einem etwas unglücklichen Design aus ...

    danke, scheinbar wars wirklich nur ein Fehler in der Parameterübergabe und wie du schon sagtest, wenn ich alles richtig gemacht hätte, hätte es funktioniert ^^

    Das diese Methoden auch in Street zur Verfügung stehen, und das ich jede Methode welche ich in irgend einer Abgeleiteten Klasse benötige auch in der Mutterklasse mit virtual deklarieren ( und da es nicht rein virtual ist ) auch initialisieren muss stört mich selbst ziemlich.

    Habe mein "design" mal geschildert, vll kann mir wer helfen. Werde mich nun nochmal damit beschäftigen und versuchen mir was besseres zu überlegen.



  • sust schrieb:

    Da Straßen zwar sehr viel Umfangreicher, jedoch sonst den Firmen und Bahnhöfen relativ ähnlich sind ( und somit viele methoden und attribute teilen) möchte ich Firmen und Bahnhöfe von den Straßen ableiten um all die gleichen Methoden und Attribute von Straßen nutzen zu können.

    Hier scheint es mir schon mal sinnvoller zu sein, das anders herum zu machen, also Strassen - die ja umfangreicher sind - von zB einer Firma - die ja weniger können muß - abzuleiten und entsprechend zu erweitern.



  • Belli schrieb:

    Hier scheint es mir schon mal sinnvoller zu sein, das anders herum zu machen, also Strassen - die ja umfangreicher sind - von zB einer Firma - die ja weniger können muß - abzuleiten und entsprechend zu erweitern.

    Bitte nicht (auch nicht anders herum). Ableitung (zumindestens die öffentliche Ableitung) sollte immer "IST EIN" Bedeuten. Ist eine Straße eine Firma? Nein!

    Ableitung wird meiner Meinung nach ohnehin viel zu häufig eingesetzt. Nichts gegen Vererbung und den sinnvollen Umgangen mit Polymorphie, aber man sollte niemals in einer Vererbung Sachen mischen die nichts miteinander zu tun haben (Es gibt immer noch die bessere Alternative: Komposition).

    cu André



  • asc schrieb:

    Belli schrieb:

    Hier scheint es mir schon mal sinnvoller zu sein, das anders herum zu machen, also Strassen - die ja umfangreicher sind - von zB einer Firma - die ja weniger können muß - abzuleiten und entsprechend zu erweitern.

    Bitte nicht (auch nicht anders herum). Ableitung (zumindestens die öffentliche Ableitung) sollte immer "IST EIN" Bedeuten. Ist eine Straße eine Firma? Nein!

    Ableitung wird meiner Meinung nach ohnehin viel zu häufig eingesetzt.

    Volle Zustimmung! Aber in diesem Fall soll wohl ein Feld eines Monopoly-Spieles zu einem zB Wasserwerk konkretisiert werden und ein weiteres zu zB Parkstrasse.
    Eine Klasse, die ein Objekt wie die Parkstrasse beschreibt, hätte durchaus alle Eigenschaften einer Klasse, die das Wasserwerk beschreiben würde (Preis, Miete, Eigentümer usw.) - zusätzliche eben die Möglichkeit, mit Häusern/Hotels bebaut zu werden ...
    In diesem Sinne wären die Bahnhöfe - wenn ich Monopoly jetzt noch richtig in Erinnerung habe, als Objekte der gleichen Klasse wie das Wasserwerk zu instantiieren ...

    Bist Du sicher, daß eine solche Hierarchie hier völlig verfehlt wäre?



  • Belli schrieb:

    Hier scheint es mir schon mal sinnvoller zu sein, das anders herum zu machen, also Strassen - die ja umfangreicher sind - von zB einer Firma - die ja weniger können muß - abzuleiten und entsprechend zu erweitern.

    Das selbe ging mir im Nachhinein auch durch den Kopf, doch da ich die Klasse street schon fertig hatte wollte ichs nimmer ändern ;P
    Ausserdem hätte die Klasse companies (durch mein oben angegebens prob.) dann noch mehr sinnlose virtuelle methoden rumfliegen als es street jetzt schon hat 😕

    asc schrieb:

    (Es gibt immer noch die bessere Alternative: Komposition).

    hmm, wenn ich mich nicht irre würde das in meinem Fall bedeuten in der Mutterklasse(zb) selbst die Objekte der andren Felder zu halten oder zu referenzieren.

    Find ich für mein Problem unpassend da ichs eigentlich recht angenehm finde eine Klasse Board zu haben welche dass Spielbrett darstellt und alle darauf befindlichen felder hält. (in form des field_ptr_[] s)

    oder hab ich das mit der Komposition falsch verstandn ?



  • pumuckl schrieb:

    Wenn du in der Mutterklasse eine Methode als virtuell deklariert hast, dann wird beim Aufruf über einen pointer (oder eine Referenz) immer die entsprechende methode der am weitesten abgeleiteten Klasse aufgerufen.

    Nene, die Runtime-Umgebung erkennt schon, von welchem Typ das Objekt ist, und ruft die Methode dieses Typs auf, und nicht etwa eine einer weiteren von diesem Typ abgeleiteten Klasse.



  • sust schrieb:

    oder hab ich das mit der Komposition falsch verstandn ?

    // Vererbung
    // Vererbung bedeutet "Ist ein"
    class A : public B {/**/};
    
    // Komposition
    // Komposition bedeutet "Enthält", "ist Teil von"
    class A
    {
        private:
            B b;
        /**/
    };
    

    Das ganze kann man natürlich auch wieder mit Schnittstellen (Im Sinne von "Interface" aus anderen Sprachen) kombinieren. In C++ sind das rein virtuelle Basisklassen.

    cu André



  • sust schrieb:

    Das selbe ging mir im Nachhinein auch durch den Kopf, doch da ich die Klasse street schon fertig hatte wollte ichs nimmer ändern ;P

    Das ist sehr schlecht. Wenn du merkst, dass eine Designentscheidung ein Fehler war, dann solltest du diesen Fehler sofort beheben und nicht aus Faulheit den fehler noch weiter ausbauen. In dem konkreten Beispiel würde ich eine weitere (abstrakte) Klasse in die Hierarchie einführen, die von der "mutteklasse" erbt und alles in sich vereint, was Bahnhöfen, E-Werk und Straßen gemeinsam ist (hypotheken, miete usw.) Am Ende leitest du dann die drei Feldtypen von dieser Klasse ab.

    Belli schrieb:

    pumuckl schrieb:

    [...]

    Nene, die Runtime-Umgebung erkennt schon, von welchem Typ das Objekt ist, und ruft die Methode dieses Typs auf, und nicht etwa eine einer weiteren von diesem Typ abgeleiteten Klasse.

    Natürlich war die am weitesten abgeleitete Klasse in der Vererbungshierarchie des dynamischen typs des Objektes gemeint. 🙄



  • pumuckl schrieb:

    Das ist sehr schlecht. Wenn du merkst, dass eine Designentscheidung ein Fehler war, dann solltest du diesen Fehler sofort beheben und nicht aus Faulheit den fehler noch weiter ausbauen.

    Ich weiß, ich mach das auch gleich noch. Desto länger ich warte umso mehr Arbeit wirds. danke ^^

    pumuckl schrieb:

    In dem konkreten Beispiel würde ich eine weitere (abstrakte) Klasse in die Hierarchie einführen, die von der "mutteklasse" erbt und alles in sich vereint, was Bahnhöfen, E-Werk und Straßen gemeinsam ist (hypotheken, miete usw.) Am Ende leitest du dann die drei Feldtypen von dieser Klasse ab.

    Ich hab selbstschon mit dem gedanken gespielt eine Klasse "BuyableField" oä. zu entwickeln. Am besten ich sag jetzt nicht das ich zu faul war 😶 Ich code da meist lieber an stellen weiter wo "neues" hinzukommt. 😕

    Mein Problem dass meine Mutterklasse (bedingt durch das pointerarray vom typ der mutterklasse, welches alle objekte der felder enthält) alle Methoden der andren Feldern als virtual deklarieren und auch initialisieren muss bleibt allerdings weiterhin bestehn. Mir fällt im moment noch keine Lösung ein 😞



  • sust schrieb:

    Ich hab selbstschon mit dem gedanken gespielt eine Klasse "BuyableField" oä. zu entwickeln. Am besten ich sag jetzt nicht das ich zu faul war

    Das bereust Du später, wenn irgendwelche Kleinigkeiten dann an vielen Stellen geändert werden müssen ...

    sust schrieb:

    Mein Problem dass meine Mutterklasse (bedingt durch das pointerarray vom typ der mutterklasse, welches alle objekte der felder enthält) alle Methoden der andren Feldern als virtual deklarieren und auch initialisieren muss bleibt allerdings weiterhin bestehn. Mir fällt im moment noch keine Lösung ein 😞

    Du mußt die Methoden in der Basisklasse nur deklarieren, aber nicht definieren oder initialisieren.
    virtual void methode(void) = 0;
    zum Beispiel bewirkt, daß Deine Basisklasse abstrakt ist, dh, Du kannst kein Objekt dieser Klasse erstellen. Alle von der Basisklasse abgeleiteten Klassen, die ihrerseits nicht abstrakt sein sollen, müssen diese Methode dann definieren, sonst sind sie auch abstrakt.



  • sust schrieb:

    Ich hab selbstschon mit dem gedanken gespielt eine Klasse "BuyableField" oä. zu entwickeln. Am besten ich sag jetzt nicht das ich zu faul war 😶 Ich code da meist lieber an stellen weiter wo "neues" hinzukommt. 😕

    Da sieht man wieder, auch Programmieren erfordert eine gewisse Disziplin 😉 Klar bringt man lieber neue Funktionalität, aber es lohnt eigentlich immer, aus dem Vorhandenen erstmal ne runde Sache zu machen, bevor man weitere Neureungen einfügt. Sonst wird das rumdoktern an den hässlichen Stellen irgendwann sehr mühselig und die Neuerungen machen auch nciht mehr so viel Spaß.

    Mein Problem dass meine Mutterklasse (bedingt durch das pointerarray vom typ der mutterklasse, welches alle objekte der felder enthält) alle Methoden der andren Feldern als virtual deklarieren und auch initialisieren muss bleibt allerdings weiterhin bestehn. Mir fällt im moment noch keine Lösung ein 😞

    Wenn ich das richtig verstehe meinst du, damit du auf den Elementen des Pointerarrays Funktionen wie kaufen(), hausBauen(), getMietpreis() etc. aufrufen kannst? Die Funktionalität haben ja nun mit gutem Grund nicht alle Felder (weil man Ereignisfelder nicht kaufen kann), deshalb sollte es auch in der Mutterklasse keine derartigen Methoden geben (wie sollte man die schließlich für die Ereignisfelder implementieren?)
    Aber: wenn du ein Feld Kaufen möchtest, musst du natürlich vorher wissen, ob das geht - es wird also für alle Felder (also in der Mutterklasse) eine Methode isBuyable() oder ähnliches geben, die standardmäßig false zurückgibt und bei BuyableField's true. Wenn du aber weißt dass das Field auf dem du grade stehst in wirklichkeit ein BuyableField ist, dann hindert dich nichts an einem Downcast:

    Field* felder[36];
    
    if (felder[i]->isBuyable())
    {
      BuyableField* kaufFeld = dynamic_cast<BuyableField*>(felder[i]); //wir wissen ja dass das geht...
      kaufFeld->kaufen();
    }
    


  • Belli schrieb:

    Du mußt die Methoden in der Basisklasse nur deklarieren, aber nicht definieren oder initialisieren.
    virtual void methode(void) = 0;
    zum Beispiel bewirkt, daß Deine Basisklasse abstrakt ist, dh, Du kannst kein Objekt dieser Klasse erstellen. Alle von der Basisklasse abgeleiteten Klassen, die ihrerseits nicht abstrakt sein sollen, müssen diese Methode dann definieren, sonst sind sie auch abstrakt.

    Ja weiß ich, aber ich kann sie leider nicht rein virtual deklarieren, da ich die methoden sonst in jeder abgeleiteten Klasse einbauen müsste.
    Wo steckt btw. der Sinn dahinter, dass ich eine rein virtuelle Methode in jeder abgeleiteten klasse implementieren muss? 😕 Macht mir das leben schwer..
    Da ich sie also nicht rein virtuell machen kann muss ich sie auch in der in der cpp file definieren. 😞

    pumuckl schrieb:

    Sonst wird das rumdoktern an den hässlichen Stellen irgendwann sehr mühselig und die Neuerungen machen auch nciht mehr so viel Spaß.

    Ich bereus auch grad bitter ^^ Bin gerade am ändern diverser Dinge wofür ich bis jetz zu Faul war... Der Aufwand wär vor einigen Tagen noch ein bruchteil davon gewesen 😕

    pumuckl schrieb:

    Wenn ich das richtig verstehe meinst du, damit du auf den Elementen des Pointerarrays Funktionen wie kaufen(), hausBauen(), getMietpreis() etc. aufrufen kannst? Die Funktionalität haben ja nun mit gutem Grund nicht alle Felder (weil man Ereignisfelder nicht kaufen kann), deshalb sollte es auch in der Mutterklasse keine derartigen Methoden geben (wie sollte man die schließlich für die Ereignisfelder implementieren?)

    Eben, es sind alle Methoden welche irgendein Feld benötigt auch in der Mutterklasse, wo sie allerdings nichts verloren haben. Natürlich werden sie dort nie aufgerufen, weil sich die Objekte durch virtual ja selbst um den richtigen Methodenaufruf kümmern.

    pumuckl schrieb:

    Aber: wenn du ein Feld Kaufen möchtest, musst du natürlich vorher wissen, ob das geht - es wird also für alle Felder (also in der Mutterklasse) eine Methode isBuyable() oder ähnliches geben, die standardmäßig false zurückgibt und bei BuyableField's true. Wenn du aber weißt dass das Field auf dem du grade stehst in wirklichkeit ein BuyableField ist, dann hindert dich nichts an einem Downcast:

    Field* felder[36];
    
    if (felder[i]->isBuyable())
    {
      BuyableField* kaufFeld = dynamic_cast<BuyableField*>(felder[i]); //wir wissen ja dass das geht...
      kaufFeld->kaufen();
    }
    

    Das klingt ganz nach des Rätsels Lösung... Sprich ich kontrolliere vorher um was für eine Art Feld es sich handelt, und caste dann vom Typ Mutterklasse direkt auf das Benötigte Feld (bzw. auf BuyableField). Somit muss ich auf die Felder nichtmehr über den Pointer der vom Typ Mutterklasse ist zugreifen.
    Ist dies "erlaubt"? Casts sind ja meistens nur nötig wenn die Grundstruktur des Programms schonmal falsch is, bzw. es gäbe meist eine schönere Lösung als casten.
    Klingt nach rel. viel Aufwand, aber wenns ein "profi" so machen würde mach ichs gern. 🙂



  • Schönen guten Tag 🙂

    Hab nun eine weiter abstrakte Klasse BuyableField eingeführt welche die gemeinsame Methoden der Kaufbaren Felder vereint.
    Nun bin ich dabei den field_ptr_ der Mutterklasse umzucasten, damit ich in der Mutterklasse nicht sämtliche Methoden deklarieren muss.
    Da ich in meiner Board Klasse jedoch sehr sehr oft mit dem field_ptr_ arbeite, ist dies ein ziemlich hoher aufwand. So einfach es vorher war mit dem field_ptr_ auf alle Felder zuzugreifen, umso umständlicher ist es jetzt jeden zugriff abzuprüfen und auf die richtige Klasse umzucasten.
    Ist dies wirklich eine Sinnvolle und schöne Lösung? Sollte ich sämtliche buyablefields gleich zu beginn im Board umcasten um aufwand zu sparen (wobei mir auch das nicht wirklich "sauber" erscheint) 😕

    danke schonmal, lg



  • [quote="pumuckl"]

    sust schrieb:

    Aber: wenn du ein Feld Kaufen möchtest, musst du natürlich vorher wissen, ob das geht - es wird also für alle Felder (also in der Mutterklasse) eine Methode isBuyable() oder ähnliches geben, die standardmäßig false zurückgibt und bei BuyableField's true. Wenn du aber weißt dass das Field auf dem du grade stehst in wirklichkeit ein BuyableField ist, dann hindert dich nichts an einem Downcast:

    Field* felder[36];
    
    if (felder[i]->isBuyable())
    {
      BuyableField* kaufFeld = dynamic_cast<BuyableField*>(felder[i]); //wir wissen ja dass das geht...
      kaufFeld->kaufen();
    }
    

    Das bedeutet aber doch, daß die Basisklasse genau wissen muß, was es für abgeleitete Klassen gibt ...
    Baut man eine weitere abgeleitete Klasse, zieht das evtl. aufwendige Änderungen in der Basisklasse nach sich.
    Da würde mir im Moment besser gefallen, eine virtuelle Methode namens 'kaufen' in der Basisklasse zu definieren, die einfach 'false' zurückgibt und von abgeleiteten buyable-Klassen entsprechend überschrieben wird.



  • Belli schrieb:

    Da würde mir im Moment besser gefallen, eine virtuelle Methode namens 'kaufen' in der Basisklasse zu definieren, die einfach 'false' zurückgibt und von abgeleiteten buyable-Klassen entsprechend überschrieben wird.

    Das beudeutet aber im Umkehrschluss, dass du in der Basisklasse sämtliche Methoden aller abgeleiteten Klassen kennen musst und damit eben alle abgeleiteten Klassen - das selbe Dilemma also.
    Ergo sollte man weder das eine noch das andere in der Basisklasse deklarieren sondern an den Stellen im Code wo man weiß, welche verschiedenen Feldtypen es gibt, rausfinden mit welchem Feldtyp man es zu tun hat. Im Header von BuyableField könnte z.B. eine (globale) Funktion bool isBuyable(Field*); deklariert werden.

    Wenn allerdings an so vielen Stellen manuell unterschieden werden muss liegt eh meist ein Designfehler vor. Am Besten gibst du uns ein Beispiel wo solch eine Unterscheidung auftaucht und wir schauen mal gemeinsam ob man die nicht automatisch lösen kann.

    Beispiel von mir:

    Field* aktuellesFeld;
    Player player;
    
    void MonopolyZug()
    {
      int wurf = Würfeln();   
      GeheVor(wurf);
      aktuellesFeld->Aktion(player); //Aktion ist virtuelle Methode von Field
    }
    
    EreignisFeld::Aktion(Player& player)
    {
      EreignisKarte karte = ZieheEreignisKarte();
      player.KartenAktion(karte);
    }
    
    GeheGefaengnisFeld::Aktion(Player& player)
    {
      player.AbInDenKnast();
    }
    
    BuyableFiled::Aktion(Player& player)
    {
      if (HatBesitzer())
      {
        player.ZahleMiete(besitzer);
      }
      else
      { player.KaufeFeld(*this); }
    }
    

    In den meisten Fällen kann man aus den Implementationen der virtuellen Methoden die klassenspezifischen Methoden (KaufeFeld usw.) aufrufen und muss daher nicht erst groß rumcasten.



  • Jaaaaaaaaaaaaaaaaaaaa ......

    Das mit der Methode Aktion schwebte mir auch schon im Kopf herum, nur dachte ich mir irgendwie, was, wenn mehrere Aktionen möglich sind.
    Aber nun, wo ich es so ausformuliert sehe, wird es mir eigentlich klarer ...
    Falls verschiedene Aktionen möglich sind, muß ja nun eine Auswahl getroffen bzw. angeboten werden ... und das kann man ja ganz wunderbar in den entsprechenden Klassen passend machen.

    Also ehrlich, jetzt wo ich es so vor mir sehe, begreife ich gar nicht, wieso ich mich vorher so schwer damit getan habe ...


Anmelden zum Antworten