你們好,老胡又在博客和你們見面了,在聊今天的主角以前,老胡先給你們講一個之前發生的故事。
ide
當老胡仍是小胡的時候,跟隨團隊一塊兒開發一款遊戲。這款遊戲是一款末日生存類遊戲,玩家能夠函數
項目開發的很順利,我那時獲得一個任務,是爲遊戲作一個新手教程,在這個教程裏面,經過一系列步驟,引導新手玩家熟悉這個遊戲。遊戲設計給出的教程包含如下步驟測試
同時要求在不用的階段顯示不一樣的提示以正確引導玩家。
考慮合成裝備算是高級玩家纔會接觸到的功能,因此暫時不打算放在新手教程裏面。this
當老大把任務交給個人時候,我感受簡單爆了,不就寫一個新手教程麼,要求又那麼明確,應該要不了多少時間。因而,一個上午事後,我交出了以下代碼。
設計
首先用一個枚舉,表示教程進行的不一樣程度code
enum TutorialState { GetGold, GetIron, KillEnemy, LevelUp }
無需多言,封裝收集到的資源數、擊殺敵人數量、角色等級和一些升級接口等對象
class Player { private int ironNum; private int goldNum; private int enemyKilled; private int level; public int IronNum => ironNum; public int GoldNum => goldNum; public int EnemyKilled => enemyKilled; public int Level => level; public void CollectIron(int num) { ironNum += num; } public void CollectGold(int num) { goldNum += num; } public void KillEnemy() { enemyKilled++; } public void LevelUp() { level++; } }
定義一個教程類,包括blog
class GameTutorial { private TutorialState currentState; private Player player; public GameTutorial(Player player) { this.player = player; } public void ShowHelpDescription() { switch (currentState) { case TutorialState.GetGold: Console.WriteLine("Please follow instruction to get gold"); break; case TutorialState.GetIron: Console.WriteLine("Please follow instruction to get Iron"); break; case TutorialState.KillEnemy: Console.WriteLine("Please follow instruction to kill enemy"); break; case TutorialState.LevelUp: Console.WriteLine("Please follow instruction to Up your level"); break; default: throw new Exception("Not Support"); } } public void ValidateState() { switch (currentState) { case TutorialState.GetGold: { if (player.GoldNum > 0) { Console.WriteLine("Congratulations, you finished Gold Collect Phase"); currentState = TutorialState.GetIron; } else { Console.WriteLine("You need to collect gold"); } break; } case TutorialState.GetIron: { if (player.IronNum > 0) { Console.WriteLine("Congratulations, you finished Iron Collect Phase"); currentState = TutorialState.KillEnemy; } else { Console.WriteLine("You need to collect Iron"); } break; } case TutorialState.KillEnemy: { if (player.EnemyKilled > 0) { Console.WriteLine("Congratulations, you finished Enemy Kill Phase"); currentState = TutorialState.LevelUp; } else { Console.WriteLine("You need to kill enemy"); } break; } case TutorialState.LevelUp: { if (player.Level > 0) { Console.WriteLine("Congratulations, you finished the whole tutorial"); currentState = TutorialState.LevelUp; } else { Console.WriteLine("You need to level up"); } break; } default: throw new Exception("Not Support"); } } }
static void Main(string[] args) { Player player = new Player(); GameTutorial tutorial = new GameTutorial(player); tutorial.ShowHelpDescription(); tutorial.ValidateState(); //收集黃金 player.CollectGold(1); tutorial.ValidateState(); tutorial.ShowHelpDescription(); //收集木頭 player.CollectIron(1); tutorial.ValidateState(); tutorial.ShowHelpDescription(); //殺敵 player.KillEnemy(); tutorial.ValidateState(); tutorial.ShowHelpDescription(); //升級 player.LevelUp(); tutorial.ValidateState(); }
運行結果
看起來一切都好。。編寫的代碼既可以根據當前步驟顯示不一樣的提示,還能夠成功的根據玩家的進度切換到下一個步驟。教程
因而,我自信滿滿的申請了code review,按照個人想法,這段代碼經過code review應該是板上釘釘的事情,誰知,老大看到代碼,差點沒背過氣去。。。稍微平復了一下心情以後,他給了我幾個靈魂拷問。接口
當時個人表情是這樣的
原本覺得如此簡單的一個功能,沒想到仍是有那麼多彎彎道道,只怪本身仍是太年輕啊!最後他悠悠的告訴我,去看看狀態模式吧,想一想這段代碼能夠怎麼重構。
對象擁有內在狀態,當內在狀態改變時容許其改變行爲,這個對象看起來像改變了其類
有點意思,看來咱們能夠把教程的不一樣步驟抽象成不一樣的狀態,而後在各個狀態內部實現切換狀態和顯示幫助文檔的邏輯,這樣作的好處是
接着咱們看看UML,
一目瞭然,在咱們的例子裏面,state就是教程子步驟,context就是教程類,內部包含教程子步驟並轉發請求給教程子步驟,咱們跟着來重構一下代碼吧。
第一步咱們須要刪除以前的枚舉,取而代之的是一個抽象類看成狀態基類,即,各個教程步驟類的基類。注意,每一個子狀態要本身負責狀態切換,因此咱們須要教程類暴露接口以知足這個功能。
abstract class TutorialState { public abstract void ShowHelpDescription(); public abstract void Validate(GameTutorial tutorial); }
重構教程類體如今如下方面
class GameTutorial { private TutorialState currentState; private Player player; public int PlayerIronNum => player.IronNum; public int PlayerLevel => player.Level; public int PlayerGoldNum => player.GoldNum; public int PlayerEnemyKilled => player.EnemyKilled; public void SetState(TutorialState state) { currentState = state; } public GameTutorial(Player player) { this.player = player; currentState = TutorialStateContext.GetGold; } public void ShowHelpDescription() { currentState.ShowHelpDescription(); } public void ValidateState() { currentState.Validate(this); } }
接着咱們建立各個子狀態表明不一樣的教程步驟
class TutorialSateGetGold : TutorialState { public override void ShowHelpDescription() { Console.WriteLine("Please follow instruction to get gold"); } public override void Validate(GameTutorial tutorial) { if (tutorial.PlayerGoldNum > 0) { Console.WriteLine("Congratulations, you finished Gold Collect Phase"); tutorial.SetState(TutorialStateContext.GetIron); } else { Console.WriteLine("You need to collect gold"); } } } class TutorialStateGetIron : TutorialState { public override void ShowHelpDescription() { Console.WriteLine("Please follow instruction to get Iron"); } public override void Validate(GameTutorial tutorial) { if (tutorial.PlayerIronNum > 0) { Console.WriteLine("Congratulations, you finished Iron Collect Phase"); tutorial.SetState(TutorialStateContext.KillEnemy); } else { Console.WriteLine("You need to collect iron"); } } } class TutorialStateKillEnemy : TutorialState { public override void ShowHelpDescription() { Console.WriteLine("Please follow instruction to kill enemy"); } public override void Validate(GameTutorial tutorial) { if (tutorial.PlayerEnemyKilled > 0) { Console.WriteLine("Congratulations, you finished enemy kill Phase"); tutorial.SetState(TutorialStateContext.LevelUp); } else { Console.WriteLine("You need to collect kill enemy"); } } } class TutorialStateLevelUp : TutorialState { public override void ShowHelpDescription() { Console.WriteLine("Please follow instruction to level up"); } public override void Validate(GameTutorial tutorial) { if (tutorial.PlayerLevel > 0) { Console.WriteLine("Congratulations, you finished the whole tutorial"); } } }
這是模式中沒有提到的知識點,通常來講,爲了不大量的子狀態對象被建立,咱們會構造一個狀態容器,以靜態變量的方式初始化須要使用的子狀態。
static class TutorialStateContext { public static TutorialState GetGold; public static TutorialState GetIron; public static TutorialState KillEnemy; public static TutorialState LevelUp; static TutorialStateContext() { GetGold = new TutorialSateGetGold(); GetIron = new TutorialStateGetIron(); KillEnemy = new TutorialStateKillEnemy(); LevelUp = new TutorialStateLevelUp(); } }
測試代碼部分保持不變,直接運行,結果和原來同樣,重構成功。
這就是狀態模式和它的使用場景,比較一下重構前和重構後的代碼,發現代碼經過重構知足了開閉原則和迪米特法則,相信重構後的代碼能經過code review吧。_ 不過狀態模式雖然好,也有本身的缺點,由於須要一個子類對應一個子狀態,那麼子狀態太多的時候,就會出現類爆炸的狀況。還請你們多注意。 做爲行爲模式之一的狀態模式,在平常開發中出現的頻率仍是挺高的,好比遊戲中常常用到的狀態機,就是狀態模式的一種應用場景,你們在平時工做中保持善於觀察的眼睛,就能學到更多的東西。 今天就講到這裏吧,謝謝你們的閱讀,下次見。