Vererbung doppelte Membervariable



  • Hi!
    Ich habe folgende Klassenstruktur im Programm.

    class Objekt {}
    class Mobil : Objekt {}
    class Angreifbar : Objekt {}
    class Einheit : Mobil, Angreifbar {}
    

    Die Gedanken dahinter:
    Was mobil ist, ist ein Objekt.
    Was angreifbar ist, ist ein Objekt.
    Eine Einheit ist mobil und angreifbar(und damit auch ein Objekt).

    Nun habe ich aber 2 mal das Objekt, und wenn ich eins ändere bleibt das andere unberührt, ich möchte die zu einem verschmelzen.





  • Warum genau müssen Mobil und Angreifbar beide von Objekt ableiten?



  • Das ist das typische Diamond-Problem. Leite einfach Mobil und Angreifbar virtual von Objekt ab.



  • FragenderUser schrieb:

    Die Gedanken dahinter:
    Was mobil ist, ist ein Objekt.
    Was angreifbar ist, ist ein Objekt.
    Eine Einheit ist mobil und angreifbar(und damit auch ein Objekt).

    Ein Tipp: Benutz nicht zu viel Vererbung. Gerade so etwas wie "angreifbar" modelliert man besser als Attribut, wenn auch nur der Hauch einer Chance besteht, dass irgendwann einmal ein Objekt existiert, dessen Angreifbarkeit sich im Laufe seines Lebens ändert.



  • Wofür brauchst du die Basisklasse "Objekt"? Wenn da nur sowas drinsteht wie der Name, dann kannst du das auch ganz weglassen. Wenn du jedesmal eine neue Basisklasse erstellst, wenn mehrere Klassen gemeinsame Variablen besitzen, endest du in einem undurchschaubaren Klassendschungel.



  • Virtual inheritance hat mir erstmal weitergeholfen.

    dot schrieb:

    Warum genau müssen Mobil und Angreifbar beide von Objekt ableiten?

    Beide haben eine Position.
    "Objekt" ist bei mir z.Z. folgendes:

    class Entity {
    	protected:
    		signed m_Position;
    	public:
    		Entity(signed Position) : m_Position(Position) {}
    		void setPosition(signed newPosition)  {
    			m_Position=newPosition;
    		}
    		signed getPosition() const {return m_Position;}
    		unsigned getDistanceTo(const Entity& Target) const { return abs(m_Position - Target.m_Position); }
    	};
    

    MFK schrieb:

    Ein Tipp: Benutz nicht zu viel Vererbung. Gerade so etwas wie "angreifbar" modelliert man besser als Attribut, wenn auch nur der Hauch einer Chance besteht, dass irgendwann einmal ein Objekt existiert, dessen Angreifbarkeit sich im Laufe seines Lebens ändert.

    Hmm, sobald etwas angreifbar ist verbinde ich schon Eigenschaften und Funktionen damit.
    Attackable sieht bei mir z.Z. so aus:

    class Attackable : virtual public Entity {
    		unsigned m_Lifepoints;
    		unsigned m_StrikebackDamage;
    	public:
    		Attackable(unsigned Lifepoints, signed Position=0, signed StrikebackDamage=0) : m_Lifepoints(Lifepoints), m_StrikebackDamage(StrikebackDamage), Entity(Position) {};
    		unsigned getLifepoints() const {return m_Lifepoints;}
    		unsigned getsHit(unsigned Damage, unsigned& OverkillDamage) {
    			OverkillDamage=0;
    			 //Modifiers here
    			if(Damage>=m_Lifepoints) {
    				OverkillDamage = Damage-m_Lifepoints;
    				m_Lifepoints=0;
    			}
    			else m_Lifepoints-=Damage;
    			return m_Lifepoints;
    		};
    
    		unsigned getStrikebackDamage() const {
    			return m_StrikebackDamage;
    		}
    
    		bool canStrikeBack() {
    			return m_StrikebackDamage != 0;
    		};
    	};
    

    daddy_felix schrieb:

    Wofür brauchst du die Basisklasse "Objekt"? Wenn da nur sowas drinsteht wie der Name, dann kannst du das auch ganz weglassen. Wenn du jedesmal eine neue Basisklasse erstellst, wenn mehrere Klassen gemeinsame Variablen besitzen, endest du in einem undurchschaubaren Klassendschungel.

    Hauptsächlich um mir nicht vorweg die Möglichkeit zu nehmen später alle Objekte in einen Container zu packen.

    Letztendlich wollte ich dann konkrete Klassen erstellen wie z.B.

    class Watchtower: public Attackable { }
       class Cannontower: public Attacker, Attackable { }
       class Guard: public Mobile, public Attacker, public Attackable { }
       //...
    


  • Lies mal was zu "component based design". 😉 Vererbung ist nur sehr bedingt bis zu dieser Art von Vielfalt geeignet.



  • FragenderUser schrieb:

    Letztendlich wollte ich dann konkrete Klassen erstellen wie z.B.

    class Watchtower: public Attackable { }
       class Cannontower: public Attacker, Attackable { }
       class Guard: public Mobile, public Attacker, public Attackable { }
       //...
    

    Viel zu viel Vererbung. Das ist eine Design-Sackgasse.

    Du zwingst dich damit, überall entweder Unmengen von überladenen Funktionen zu benutzen, oder (noch schlimmer) ständig den Typ zur Laufzeit zu prüfen.

    Viel Spaß mit temporären Effekten, die ein Objekt zeitweise bewegungsunfähig oder unangreifbar machen.



  • Hauptsächlich um mir nicht vorweg die Möglichkeit zu nehmen später alle Objekte in einen Container zu packen.

    Ich würde vorschlagen, Dir vorher Gedanken darüber zu machen, wann Du etwas in einen Container stecken möchtest. Häufig resultiert das Klassendesign daraus nämlich.

    Vererbung ist sehr starr. Denn man hat da eben nur eine Richtung vorgegeben und diese erhält dann automatisch sehr hohen Wert. Wenn Du also etwas von "Angreifer" erben lässt, dann ist es vor allem ein Angreifer. Ist es denn "mehr" ein Angreifer als etwas anderes, wovon es erbt? In C++ gibt es Mehrfachvererbung, daher prinzipiell auch mehrere Richtungen, aber meistens macht man sich damit mehr Probleme als man löst.

    So auf den ersten Blick würde ich etwas Attackierbares und etwas Bewegbares wohl zwar in einzelne Objekte auslagern, dann aber so etwas wie "Monster" nicht davon ableiten lassen, sondern das über Komposition regeln, also Attackierbares und Bewegbares als Attribute reinnehmen. Das schafft fast immer mehr Flexibilität, außer Du brauchst eben Polymorphie. Das nehme ich so als Grundsatz.



  • Ja, habe jetzt schon beim Schreiben einer konkreten Klasse gemerkt das es so nicht weitergehen kann und das ich beim implementieren neuer Dinge hier und da was abändern muss.

    So grob sieht das Component Design so aus als würde man eine Überklasse erstellen:

    class Entity {
          Cmp_Attackable* Attackable; //isActive Flag zeigt an ob Entity Angreifbar ist
          Cmp_Placement* Placement;
          //...
          EntityComponent* getNotListetComponent(string Name); //Das Ergebnis muss dann gecastet werden
       }
    

    Ich werd mich mal weiter einlesen, klingt nach dem was ich suche.



  • Meiner Meinung nach sollte "mobil" meistens - mal die Einwände ignorierend, die andere gebracht haben - gar keine eigene Klasse sein. Der Unterschied zwischen einem stationären und einem mobilen Objekt ist eigentlich nicht, das sich ersteres nicht bewegen kann, sondern eher, dass es sich einfach nur nicht bewegt. Ich sehe im Normalfall eigentlich keinen Grund, sich durch das Design die nicht-Bewegbarkeit aufzuzwingen.


Anmelden zum Antworten