State Pattern Implementierung - friend nötig?
-
ich habe das problem folgendermassen gelöst, ist zwar nicht schön und ich wäre dankbar für eine bessere lösung, aber es läuft erstmal:
- es gibt eine klasse um die ganzen game states zu managen (GameStateManager)
- es gibt eine globale variable (String, enum, egal was, hauptsache es identifiziert die gamestates eindeutig)
- jeder game state kann auf die globale variable zugreifen und einen anderen state damit einläuten
- die manager klasse guckt in jedem update durchlauf ob diese variable verändert wurde und wechselt demnach den zustand (oder auch nicht;))schön ist das nicht, aber war mir lieber als die state manager klasse überall als parameter mitzugeben. vielleicht wäre ein zeiger in jedem zustand auf diese klasse noch ganz gut
-
Zustand als enum oder ähnliches anzugeben ist genau das, was das State Pattern umgehen will. Gibt es einen neuen Zustand muss ich das enum anpacken und irgendeine wilde switch case Anweisung. Beim State Pattern ist jeder Zustand als Unterklasse implementiert. Jede State Klasse implementiert igendeine Schnittstelle mit der man irgendeine Aktion ausführt, die abhängig vom Zustand ist. Dann gibt es irgendeinen
Context(was du hier wohl fälschlicherweise als GameState benennst) der einen Zustandszeiger hat, an dessen Schnittstelle er weiterleitet. Außerdem hat derContexteine öffentliche Schnittstelle um seinen Zustand zu ändern. meistens sind es die konkreten Zustände selber, die diese Schnittstelle verwenden (so wie du es ja auch vorhast).class GameContext; class State { public: virtual void update(GameContext * context, int deltaMillis) = 0; }; class GameContext { public: void update(int deltaMillis); void changeState(State* s); private: State * activeState; }; class PlayingState { public: void update(GameContext * context, int deltaMillis); }; class MenuState { public: void update(GameContext * context, int deltaMillis); }; void GameContext::update(int deltaMillis) { activeState->update(this, deltaMillis); } void PlayingState::update(GameContext * gameState, int deltaMillis) { if(menuHasJustBeenOpened()) { gameState->changeState(new MenuState); } }Eigentlich warst Du ja schon nah dran, nur dass die eben diese Schnittstelle fehlt (und GameState der falsche Name ist). Denn GameContext ist nicht für die konkreten States zuständig, sondern delegiert nur an eine abstrakte Schnittstelle.
-
Ich finde das state pattern für ein große Anzahl von Zuständen eher unübersichtlich, da die Zustandsübergänge über die Klassen "verschmiert" sind. Bei vielen möglichen Zuständen und entsprechend vielen Triggern hat eine zentrale Verwaltung über eine Zustandsübergangstabelle den Vorteil, dass das Hinzufügen von neuen States und Triggern zentral gepflegt werden kann und nicht in jeder Klasse, die einen Trigger verarbeitet, separat verwaltet werden muss.
Code für so eine Implementierung gibt's im Netz zuhauf, bei Bedarf kann ich gerne was posten.
-
brotbernd schrieb:
Zustand als enum oder ähnliches anzugeben ist genau das, was das State Pattern umgehen will.
Jein. Der Zustand ALS enum, aber nicht das setzen der Zustände ÜBER enum.
Dennoch macht es oft Sinn kein enum zu verwenden, aber ein enum in setState() widerspricht nicht dem State Pattern.
-
Shade Of Mine schrieb:
Dennoch macht es oft Sinn kein enum zu verwenden, aber ein enum in setState() widerspricht nicht dem State Pattern.
Zeig mal bitte was du meinst. Ich verstehe das was du sagst so
enum StateType {/*..*/}; void Context::SetState(StateType state) { switch (state) case STATE_A: this->SetState(new StateA); case STATE_B: this->SetState(new StateB); ... }Ich glaub übrigens dass ich an der ursprünlichen Frage etwas vorbei geredet hatte, das soll aber erst nochmal bestätigt werden;)
-
Ich sehe nur wenig unterschied zwischen deinem code und meinem.
Du hast GameState zu GameContext umbenannt.
Und noch die Definition der Klasse State hingeschreiben, die ich auch verwendet hatte (ohne die Definition hinzuschreiben).
Das ist ja eigentlich nur eine Benennungssache, aber ich sehe ein, dass Context hier evtl. etwas klarer ist.Das eigentliche Problem war, wie ich sage, zu welchem State ich wechseln will.
Du hast das mit new gelöst, was ich ja eigentlich umgehen wollte, weil es nicht wirklich sinnvoll ist, die states jedes mal neu zu erstellen.
Ich hatte mir ja gedacht, alle states, die man braucht im GameState (oder jetzt heißt es GameContext) zu speichern und dann müssen die konkreten States irgendeine Möglichkeit kriegen, zwischen den states zu wechseln.
Per enum würde funktionieren, ist aber mit etwas aufwand verbunden und finde ich nicht besonders gut.
Friend würde auch gehen, ist aber auch nicht so das wahre...
Viele andere Möglichkeiten fallen mir nicht mehr ein.
-
Q schrieb:
Du hast das mit new gelöst, was ich ja eigentlich umgehen wollte, weil es nicht wirklich sinnvoll ist, die states jedes mal neu zu erstellen.
Ja, das war mir auch erst nach dem Post aufgefallen, dass das wohl dein eigentliches Problem ist.
Zunächst mal die Frage: Wieso ist es nicht sinnvoll? Haben die States selber State (höhö, also einen Zustand der erhalten bleiben soll). Wenn ja, dann sollten die States aber getrennt vom Context Objekt gespeichert werden.
-
Ja, die states haben selber State.
Bisher gibt es eigentlich nur
PlayingStateund der beinhaltet z.B. ein oder mehrere Level-Objekte, die wiederum state haben.
Außerdem ist der Game(State)Context ein Attribut der Game-Klasse bei mir.
Die Game-Klasse ist für allgemeinere Aufgaben zuständig, wie öffnen eines Fenster, initialisieren des netzwerks, messen der Zeit usw.Alle GameStates brauchen dann einen Pointer/ eine Referenz auf Teile der Game-Klasse:
-Fenster
-Netzwerkobjekte
-evtl. mehrDiese Pointer könnte ich alle im GameStateContext speichern.
Aber alle konkreten GameStates brauchen darauf auch Zugriff und müssen außerdem auch auf einen neuen State wechseln können.
Alles kompliziert...
Ich hab noch keine gute Lösung gefunden.Wenn ja, dann sollten die States aber getrennt vom Context Objekt gespeichert werden.
Wo soll ichs denn speichern?
Die Levels z.B. sind ja im konkreten State-Objekt gespeichert.
Die Handles auf Window usw braucht eigentlich jede State, deswegen wäre speichern im Kontext möglich oder auch speichern in jeder konkreten Klasse.Ich komm durcheinander

Hoffentlich habt ihr nochn paar gute Vorschläge

@Brotbernd: Danke, dass du mir versuchst zu helfen!
-
brotbernd schrieb:
Shade Of Mine schrieb:
Dennoch macht es oft Sinn kein enum zu verwenden, aber ein enum in setState() widerspricht nicht dem State Pattern.
Zeig mal bitte was du meinst. Ich verstehe das was du sagst so
enum StateType {/*..*/}; void Context::SetState(StateType state) { switch (state) case STATE_A: this->SetState(new StateA); case STATE_B: this->SetState(new StateB); ... }Ich glaub übrigens dass ich an der ursprünlichen Frage etwas vorbei geredet hatte, das soll aber erst nochmal bestätigt werden;)
Genau das meine ich.
StatePattern hilft ja zu verhindern dass das switch(state) überall stattfinden muss - aber an einer zentralen Stelle ist es kein Problem.
-
Shade Of Mine schrieb:
brotbernd schrieb:
Shade Of Mine schrieb:
Dennoch macht es oft Sinn kein enum zu verwenden, aber ein enum in setState() widerspricht nicht dem State Pattern.
Zeig mal bitte was du meinst. Ich verstehe das was du sagst so
enum StateType {/*..*/}; void Context::SetState(StateType state) { switch (state) case STATE_A: this->SetState(new StateA); case STATE_B: this->SetState(new StateB); ... }Ich glaub übrigens dass ich an der ursprünlichen Frage etwas vorbei geredet hatte, das soll aber erst nochmal bestätigt werden;)
Genau das meine ich.
StatePattern hilft ja zu verhindern dass das switch(state) überall stattfinden muss - aber an einer zentralen Stelle ist es kein Problem.das meinte ich auch, aber nicht mit new State sondern mit den vorhandenen Membern im Ausgangspost.
Alternativ eben die States nicht als Member machen sondern von aussen an setState geben
-
Q schrieb:
Hallo zusammen,
ich möchte das State-Pattern einsetzen, habe aber ein paar Probleme mit der Implementierung.
Das sieht im Moment ungefähr so aus:
class GameState { public: void update(int deltaMillis); void draw(sf::Window&) const; private: State * activeState; PlayingState playingState; MenuState menuState; //... }; void GameState::update(int deltaMillis) { activeState->update(this, deltaMillis); } ... void PlayingState::update(GameState * gameState, int deltaMillis) { if(menuHasJustBeenOpened()) { //dafür brauch man friend gameState->setState(gameState.menuState); } }Eine alternative wäre, bei jedem übergang mit new ein neues state-objekt zu erzeugen, aber eigentlich brauche ich nur eine instanz...
Dafür würde wieder Singleton funktionieren, aber wirklich gut finde ich das hier nicht.
Außerdem müsste setState auch public sein, wenn man kein friend macht.Wie würdet ihr das lösen?
Wirklich einfach friend für jede State die man haben will?Warum nicht einfach?
void PlayingState::update(GameState * gameState, int deltaMillis) { if(menuHasJustBeenOpened()) { //dafür brauch man kein friend gameState->setState(gameState.getMenuState()); } }