Frage zum State Pattern in C++
-
Moin!
Ich hab da eine Anwendung für die sich das State Pattern eignet. Nach diverser Recherche, u.a. http://www.c-plusplus.net/forum/242984 von Markus, bin ich mir jetzt allerdings unsicher, wie man das in C++ realisiert.
In den meisten Tutorials gibt es z.B. eine Klasse Monster, die einen bestimmten Zustand besitzt:
class Monster { Zustand *pAktuellerZustand; public: void MachWas() { pAktuellerZustand->MachWas() }; void aendereZustand(Zustand *pNeuerZustand); // [...] };Und eine Basisklasse für die Zustände:
class Zustand{ public: Zustand(Monster *pMonster); virtual void MachWas() = 0; virtual ~Zustand(){}; protected: Monster *pMonster; };Die Zustandsänderung erfolgt in den Zuständen selber, was ich so auch richtig finde. D.h. innerhalb einer Methode einer abgeleiteten Zustandsklasse wird geprüft, ob Bedingungen erfüllt sind und dann wird im Zustand selber ein neuer Zustand gesetzt. Das wird immer so realisiert:
z.B. im Zustand Aengstlich:
void Zustand_Aengstlich::MachWas() { // Renne wild umher if(GesundheitGefunden == true) { pMonster->aendereZustand(new Zustand_Kampfbereit(pMonster)); } }aendereZustand ist wie folgt definiert:
void Monster::aendereZustand(Zustand *pNeuerZustand) { delete pAktuellerZustand; pAktuellerZustand= pNeuerZustand; }Was ich nicht raffe:
Aufgerufen wird das ja wie folgt:
Monster::MachWas() ruft vom aktuellen Zustand Zustand::MachWas() auf. Innerhalb dieser Methode wird dann vom Monster Monster::aendereZustand() aufgerufen, wo die zugehörige Klasse/der Zustand der aufrufenden Methode ja gelöscht wird:
delete pAktuellerZustand;Erst dann wird der neue Zustand gesetzt. Ist das überhaupt erlaubt? Schließlich könnte ja nach dem Setzen des Zustandes in der Zustand::MachWas() noch auf Klassenattribute o.ä. zugegriffen werden

Wie wird das State Pattern korrekt implementiert? Oder ist das so korrekt?
-
Sofern möglich kannst du die abgeleiteten States als Singletone implementieren, dann musst du diese nicht so unsauber löschen. Der Code ist halt nicht sicher. Du könntest std::swap verwenden und danach löschen.
-
I want MultiPartMonsters (aka 'More Brain to MonsterS):
Ich würde da warscheinlich eher beim "Erschaffen" des Monsters alle 'mögliche' Zustände mitgeben und intern einfach nur umschalten ... nicht zuweisen und zerstören
enum Dir { moveN, moveE, moveS, moveW, moveU, moveD }; enum State { deadState = 0, aliveState, hungryState, sleepyState, angryState, curiousState, lookingForIcecreamState, cryingState, surprisedState}; enum Event { evNothing = 0, evHpLoss, evHpGain, evHungryer, evFoodFound, evPlaymateFound, evScornedBySomeone, evDunnoWhatEver }; class MultiPartMonster; class Brain { MultiPartMonster * myMonster; std::set<State> states; State curState; public: Brain(std::set<State> ls) : myMonster(0), states(ls) { curState = *states.begin(); } void setMonster(MultiPartMonster &m) { if (!myMonster) myMonster = &m; } // no brain surgery allowed void addState(const State & s) { states.insert(s); } void process(const Event &e); }; struct Heart { int liveForce; }; struct Stomach { int hunger; }; struct Legs { int speed; void walk() {}; void flee() {}; }; struct Kidneys { int pullerAlarm; void piss() {} }; struct Arms { int Strength; void attack() {} }; class MultiPartMonster { friend class Brain; Brain brain; Heart heart; Stomach stomach; Legs legs; Arms arms; Kidneys kidneys; // ... public: MultiPartMonster(const std::set<State> & ls) : brain(Brain(ls)) { brain.setMonster(*this); } void addState(const State& s) { brain.addState(s); } void newTurn(const Event & e) { brain.process(e); } }; /* use/manipulate/query Monstermembers depending on currentState and incoming event */ void Brain::process(const Event &e) { if (e == evScornedBySomeone) { if (curState == angryState) { myMonster->arms.Strength = 30; myMonster->arms.attack(); } if (curState == curiousState) { myMonster->legs.walk(); } } }
-
Gerade das letzte Beispiel zeigt einen großen Nachteil, wenn man (bei vielen, komplexen Zuständen!) kein State Pattern verwendet. Das Aufschlüsseln der einzelnen Fälle und Aktionen wird komplex, kompliziert, möglicherweise fehleranfälliger, schwerer wartbar (da unübersichtlich) und erweiterbar (Änderung direkt an der Schnittstelle).
Zudem: Was passiert, wenn es spezifische Zustandsvariablen gibt, sprich für States gibt es eigene Member. Willst du die alle in deine Klasse packen, die zustandsbehaftet ist? Gerade Zustände sollten nach aussen hin (zum Client) nicht sichtbar sein.
@TE: Das Löschen und Neu-Setzen von Zuständen hast _du_ ja in der Hand, also dürfte auch nicht keine Gefahr bestehen. Der Vorschlag von HighLigerBiMBam dagegen ist gut, verhindert, dass im Fall des Falles ein undefinierter Zustand existiert.
HTH