你们好,老胡又在博客和你们见面了,在聊今天的主角以前,老胡先给你们讲一个之前发生的故事。
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吧。_ 不过状态模式虽然好,也有本身的缺点,由于须要一个子类对应一个子状态,那么子状态太多的时候,就会出现类爆炸的状况。还请你们多注意。 做为行为模式之一的状态模式,在平常开发中出现的频率仍是挺高的,好比游戏中常常用到的状态机,就是状态模式的一种应用场景,你们在平时工做中保持善于观察的眼睛,就能学到更多的东西。 今天就讲到这里吧,谢谢你们的阅读,下次见。