系統的狀態,物件的狀態,連線的狀態,等等等等... 目前的狀態決定我們什麼可以做,什麼不能做,可以的話又是怎麼做呢?狀態的管理一直以來都是個棘手的問題,今天我就來分享一下我的一些經驗與心得吧。
最開始的時候,我們可能都是這樣管理狀態的: 使用一個數值來代表。
假設我們有一個遊戲,它有四種狀態
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)
沒有留言:
張貼留言