欧博网址:让工具拥有状态——C#中的状态模式

admin 3个月前 (07-02) 科技 57 0

人人好,老胡又在博客和人人碰头了,在聊今天的主角之前,老胡先给人人讲一个以前发生的故事。
 

真实的故事

当老胡照样小胡的时刻,追随团队一起开发一款游戏。这款游戏是一款末日生计类游戏,玩家可以

  • 网络资源,两种,一种金子,一种铁。
  • 升级自身
  • 击杀敌人
  • 用资源合成装备

项目开发的很顺遂,我那时获得一个义务,是为游戏做一个新手教程,在这个教程内里,通过一系列步骤,指导新手玩家熟悉这个游戏。游戏设计给出的教程包罗以下步骤

  • 网络金子
  • 网络铁
  • 击杀敌人
  • 升级

同时要求在不用的阶段显示差别的提醒以正确指导玩家。
思量合成装备算是高级玩家才会接触到的功效,以是暂时不计划放在新手教程内里。

当老大把义务交给我的时刻,我感受简朴爆了,不就写一个新手教程么,要求又那么明确,应该要不了若干时间。于是,一个上午事后,我交出了如下代码。
 

我的代码
界说枚举示意教程进度

首先用一个枚举,示意教程举行的差别水平

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++;
    }
}

 

界说教程类

界说一个教程类,包罗

  • 显示辅助文字以协助玩家通过当前教程步骤
  • 判断玩家是否已经完成当前教程步骤,若是,切换到下一个步骤直到完成教程
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应该是板上钉钉的事情,谁知,老大看到代码,差点没背过气去。。。稍微平复了一下心情之后,他给了我几个灵魂拷问。

  • GameTutorial需要知道各个步骤的知足条件和提醒,它是不是知道的太多了?这相符迪米特规则吗?
  • 若是我们游戏之后新增一个教程步骤,指导玩家升级武器,是不是GameTutorial需要修改?能有设施规避这种新增的改动吗?
  • 若是我们要修改现在的教程步骤之间的顺序关系,GameTutorial是不是又不能制止要被动刀?能有设施只管削减这种修改的工作量吗?
  • Switch case 在现有的情形下已经云云长,若是我们再加入新的步骤,这个方式会酿成又臭又长的裹脚布吗?

那时我的脸色是这样的

原本以为云云简朴的一个功效,没想到照样有那么多弯弯道道,只怪自己照样太年轻啊!最后他悠悠的告诉我,去看看状态模式吧,想想这段代码可以怎么重构。
 

状态模式进场

界说

工具拥有内在状态,当内在状态改变时允许其改变行为,这个工具看起来像改变了其类

有点意思,看来我们可以把教程的差别步骤抽象成差别的状态,然后在各个状态内部实现切换状态和显示辅助文档的逻辑,这样做的利益是

  • 相符迪米特规则,把各个步骤所对应的逻辑推迟到子类,教程类就不需要领会每个步骤的逻辑细节,同时隔离了教程类和状态类,确保状态类的修改不会影响教程类
  • 相符开闭原则,若是新添加步骤,我们仅仅需要添加步骤子类并修改相邻的步骤切换逻辑,教程类无需任何改动

接着我们看看UML,

一目了然,在我们的例子内里,state就是教程子步骤,context就是教程类,内部包罗教程子步骤并转发请求给教程子步骤,我们随着来重构一下代码吧。
 

代码重构
建立状态基类

第一步我们需要删除之前的枚举,取而代之的是一个抽象类看成状态基类,即,各个教程步骤类的基类。注重,每个子状态要自己卖力状态切换,以是我们需要教程类露出接口以知足这个功效。

abstract class TutorialState
{
    public abstract void ShowHelpDescription();
    public abstract void Validate(GameTutorial tutorial);
}

 

重构教程类

重构教程类体现在以下方面

  • 添加内部状态外面当前处于哪个步骤,在组织函数中给予初始值
  • 露出接口以让子状态能修改当前状态以完成状态切换
  • 由于需要子状态能接见玩家当前数据以判断是否能切换状态,需要新加接口以制止方式链
  • 修改ShowHelpDescriptionValidateState的逻辑,直接转发方式挪用至当前状态
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吧。_
不外状态模式虽然好,也有自己的瑕玷,由于需要一个子类对应一个子状态,那么子状态太多的时刻,就会泛起类爆炸的情形。还请人人多注重。
作为行为模式之一的状态模式,在一样平常开发中泛起的频率照样挺高的,好比游戏中经常用到的状态机,就是状态模式的一种应用场景,人人在平时工作中保持善于考察的眼睛,就能学到更多的器械。
今天就讲到这里吧,谢谢人人的阅读,下次见。

,

欧博开户

欢迎进入欧博开户平台(Allbet Game):www.aLLbetgame.us,欧博开户平台开放欧博Allbet开户、欧博Allbet代理开户、欧博Allbet电脑客户端、欧博AllbetAPP下载等业务。

Allbet声明:该文看法仅代表作者自己,与本平台无关。转载请注明:欧博网址:让工具拥有状态——C#中的状态模式

网友评论

  • (*)

最新评论

文章归档

站点信息

  • 文章总数:453
  • 页面总数:0
  • 分类总数:8
  • 标签总数:815
  • 评论总数:116
  • 浏览总数:3896