系統的狀態,物件的狀態,連線的狀態,等等等等... 目前的狀態決定我們什麼可以做,什麼不能做,可以的話又是怎麼做呢?狀態的管理一直以來都是個棘手的問題,今天我就來分享一下我的一些經驗與心得吧。
最開始的時候,我們可能都是這樣管理狀態的: 使用一個數值來代表。
假設我們有一個遊戲,它有四種狀態
enum eGameState{
eLoadingGame,
eTitleScreen,
eOptionScreen,
ePlaying,
eScoreDisplay,
};
我們可能有一個叫GameApp的class,define成這樣class GameApp {
eGameState game_state_; // 目前遊戲狀態
public:
GameApp()
: game_state_(eLoadingGame)
{
}
void handle_keyboard_input(InputEvent &e);
};在handle_keyboard_input()中用一串if else來做對應目前遊戲狀態的工作
void GameApp::handle_keyboard_input(InputEvent &e) { if (game_state_ == eLoadingGame) { return; // 遊戲載入中,不管鍵盤輸入 } else if (game_state_ == eTitleScreen) { ... // 選擇開始遊戲、遊戲設定、離開等 } else if (game_state_ == eOptionScreen) { ... } else if (game_state_ == ePlaying) { ... }
一切都很合理。但假設我們還有 handle_mouse_input(), handle_gamepad_input()等等,程式可能變成這樣
void GameApp::handle_keyboard_input(InputEvent &e) { if (game_state_ == eLoadingGame) { return; } else if (game_state_ == eTitleScreen) { ... } else if (game_state_ == eOptionScreen) { ... } else if (game_state_ == ePlaying) { ... } void GameApp::handle_mouse_input(InputEvent &e) { if (game_state_ == eLoadingGame) { return; } else if (game_state_ == eTitleScreen) { ... } else if (game_state_ == eOptionScreen) { ... } else if (game_state_ == ePlaying) { ... } void GameApp::handle_gamepad_input(InputEvent &e) { if (game_state_ == eLoadingGame) { return; } else if (game_state_ == eTitleScreen) { ... } else if (game_state_ == eOptionScreen) { ... } else if (game_state_ == ePlaying) { ... }光加三個method應該就夠你覺得煩了,每加一個需要依目前的game_state_來決定採取的行動的地方都要加這一串 if .. else ...。這還不是最煩的,頂多copy/paste就好了嘛,麻煩的是,如果有一天我們要加一個新的狀態,我們就必須找到所有使用到game_state_的地方,加上新的判斷,要是有漏掉的地方,程式依然在表面上可以正常執行,但實際上卻可能埋了一個未爆彈等著那天出其不意的嚇死你...或讓你一頭霧水。
避免重複的程式碼是寫出可維護的程式的最基本步驟。
要避免用一堆if...else判斷來做狀態的對應動作,人們發明了一個叫state pattern的東西。簡單的說,就是把每個狀態都變成一個物件,根據狀態的變化切換目前的狀態物件,那些原本的if...else改成只要呼叫目前狀態物件的method就行了。
我們的範例可能就變成下面這樣
class GameState
{
public:
virtual void handle_keyboard_input(GameApp &app, InputEvent &e) = 0;
virtual void handle_mouse_input(GameApp &app, InputEvent &e) = 0;
virtual void handle_gamepad_input(GameApp &app, InputEvent &e) = 0;
};
class StateLoadingGame: public GameState
{
public:
virtual void handle_keyboard_input(GameApp &app, InputEvent &e)
{ }
virtual void handle_mouse_input(GameApp &app, InputEvent &e)
{ }
virtual void handle_gamepad_input(GameApp &app, InputEvent &e)
{ }
};
class StateTitleScreen: public GameState
{
public:
virtual void handle_keyboard_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_mouse_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_gamepad_input(GameApp &app, InputEvent &e)
{
...
}
};
class StateOptionScreen: public GameState
{
public:
virtual void handle_keyboard_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_mouse_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_gamepad_input(GameApp &app, InputEvent &e)
{
...
}
};
class StatePlaying: public GameState
{
public:
virtual void handle_keyboard_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_mouse_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_gamepad_input(GameApp &app, InputEvent &e)
{
...
}
};
class StateScoreDisplay: public GameState
{
public:
virtual void handle_keyboard_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_mouse_input(GameApp &app, InputEvent &e)
{
...
}
virtual void handle_gamepad_input(GameApp &app, InputEvent &e)
{
...
}
};
GameApp class變成
class GameApp {
GameState *game_state_; // 目前遊戲狀態物件
friend class StateLoadingGame;
friend class StateTitleScreen;
friend class StateOptionScreen;
friend class StatePlaying;
friend class StateScoreDisplay;
public:
GameApp()
: game_state_(new StateLoadingGame)
{}
void handle_keyboard_input(InputEvent &e);
void handle_mouse_input(InputEvent &e);
void handle_gamepad_input(InputEvent &e);
};
void GameApp::handle_keyboard_input(InputEvent &e)
{
game_state_->handle_keyboard_input (*this, e);
}
void GameApp::handle_mouse_input(InputEvent &e)
{
game_state_->handle_mouse_input (*this, e);
}
void GameApp::handle_gamepad_input(InputEvent &e)
{
game_state_->handle_gamepad_input (*this, e);
}
有沒有?程式變得很乾淨?真是太棒了!如果你要給GameApp加新的與狀態相關的method,就去每個state class中加對應的method。如果你要加新的state,那就建立新的state class。再也不用維護一堆if...else...了,一切都變成簡單而美好,是不是?
然而,如果事情這麼簡單就好了。
使用雖簡單,但是你怎麼進行與維護狀態的轉換?如果你的物件狀態不像GameApp只有五個,而是五十個呢?
關於狀態的話題我們將在下一話中繼續...
有興趣的朋友去買本design patterns的書來充實一下吧。(雖然這本我沒看過 XD)
沒有留言:
張貼留言