亲宝软件园·资讯

展开

C#对象状态模式

老胡写代码 人气: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应该是板上钉钉的事情,谁知,老大看到代码,差点没背过气去。。。稍微平复了一下心情之后,他给了我几个灵魂拷问。

本来以为如此简单的一个功能,没想到还是有那么多弯弯道道,只怪自己还是太年轻啊!最后他悠悠的告诉我,去看看状态模式吧,想想这段代码可以怎么重构。

状态模式出场

定义

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

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

接着我们看看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();
    }
}

测试代码部分保持不变,直接运行,结果和原来一样,重构成功。

结语

加载全部内容

相关教程
猜你喜欢
用户评论