勇哥谈谈状态模式(State Pattern)

勇哥注:

状态模式(State Pattern)最简单的理解就可以用来消灭那种很长的if else,或者case。

官方说法是:当一个对象内在状态改变时允许改变其行为,这个对象看起来像是改变了其类

红绿灯就很像是一个状态模式,

红灯之后是绿灯,绿灯之后是黄灯,黄灯之后是红灯,

这就很符合状态模式的一个逻辑


引用《大话设计模式》的说法:

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况。
把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。


按类图实现的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 按设计模式类图的标准实现
{
    class Program
    {
        static void Main(string[] args)
        {
            //需要置初始状态
            Context c = new Context(new ConcreateStateA());
            //不断请求,同时更改状态
            c.Request();
            c.Request();
            c.Request();
            Console.ReadKey();
        }
    }


    public abstract class State
    {
        public abstract void Handle(Context c);
    }

    public class ConcreateStateA : State
    {
        public override void Handle(Context c)
        {
            //设置A状态的下一个状态是B
            c.SetState(new ConcreateStateB());
           
        }
    }

    public class ConcreateStateB : State
    {
        public override void Handle(Context c)
        {
            //设置B状态的下一个状态是C
            c.SetState(new ConcreateStateC());

        }
    }

    public class ConcreateStateC : State
    {
        public override void Handle(Context c)
        {
            //设置C状态的下一个状态是A
            c.SetState(new ConcreateStateA());
        }
    }

    public class Context
    {
        private State currentState;

        public Context(State state)
        {
            this.currentState = state;
        }

        public void SetState(State s)
        {
            //设置新状态
            Console.WriteLine($"状态: {s.GetType().Name}");
            this.currentState = s;
        }

        public void Request()
        {
            //对请求做处理,并设置下一个状态
            currentState.Handle(this);
        }
    }


}


image.png


演示:应急照明灯


按上面的原理,勇哥举个栗子。

勇哥买了个车用应急照明灯,依次按它的功能按钮,是下面的效果:

强光
弱光
闪烁
关闭

请用状态模式实现。


勇哥的答案:

image.png

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 应急照明灯
{
    class Program
    {
        static void Main(string[] args)
        {
            Context c = new Context(new 强光());
            c.Request();
            c.Request();
            c.Request();
            c.Request();

            Console.ReadKey();
        }
    }

    public class Context
    {
        private LightState currentState;
        public Context(LightState state)
        {
            this.currentState = state;
        }

        public void SetState(LightState state)
        {
            Console.WriteLine($"{state.GetType().Name}");
            this.currentState = state;
        }

        public void Request()
        {
            this.currentState.DoAction(this);
        }
    }

    public abstract class LightState
    {
        public abstract void DoAction(Context c);
    }

    public class 强光 : LightState
    {
        public override void DoAction(Context c)
        {
            c.SetState(new 弱光());
        }
    }

    public class 弱光 : LightState
    {
        public override void DoAction(Context c)
        {
            c.SetState(new 闪烁());
        }
    }

    public class 闪烁 : LightState
    {
        public override void DoAction(Context c)
        {
            c.SetState(new 关闭());
        }
    }


    public class 关闭 : LightState
    {
        public override void DoAction(Context c)
        {
            c.SetState(new 强光());
        }
    }

}


image.png



状态模式的好处?

什么时候用状态模式?

下面是来自《大话设计模式》的回答:


状态模式好处?


状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分割开来[DP]。

将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreateState中,

所以通过定义新的子类可以很容易地增加新的状态和转换[DP]。


说白了,这样做的目的就是为了消除庞大的条件分支语句,大的分支判断会使得它们难以修改和扩展,

就像最早的刻版印刷一样,任何改动和变化都是致使了。

状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互间的依赖。


什么时候应该用状态模式呢?


当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,

就可以考虑使用状态模式了。

另外如果业务需求某项业务有多个状态,通常都是一些枚举常量,

状态的变化都是依靠大量的多分支判断语句来实现,此时应该考虑将每一种业务状态定义为一个State的子类。

这样这些对象就可以不依赖于其它对象而独立变化了,某一天客户需要更改需求,

增加或减少业务状态或改变以状态流程,对你来说都是不困难的事。





演示:工作状态切换



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication19
{
    /*
    描述下面的状态:
    上午上班时间<12点
    12点<午休时间<13点
    13点<下午上班时间<18点
    下班时间>18点
    睡觉时间>22点
    */

    public enum WorkStatusEnum
    {
        上午上班时间, 午休时间, 下午上班时间, 下班时间, 睡觉时间
    }


    class Program
    {
        static void Main(string[] args)
        {
            //int hour = 9;
            //if (hour < 12)
            //    Console.WriteLine($"{WorkStatusEnum.上午上班时间.ToString()}");
            //else if (hour < 13)
            //    Console.WriteLine($"{WorkStatusEnum.午休时间.ToString()}");
            //else if (hour < 18)
            //    Console.WriteLine($"{WorkStatusEnum.下午上班时间.ToString()}");
            //else if (hour < 22)
            //    Console.WriteLine($"{WorkStatusEnum.下班时间.ToString()}");
            //else
            //    Console.WriteLine($"{WorkStatusEnum.睡觉时间.ToString()}");

            var w = new Work();
            w.Hour = 12;
            w.OutWrokState();


            Console.ReadKey();
        }
      

    }

    public class Work
    { 
        public int Hour { get; set; }
        private State currentState;

        public Work()
        {
            currentState = new 上午上班时间();
        }

        public void SetState(State s)
        {
            this.currentState = s;
        }
        public void OutWrokState()
        {
            currentState.OutWorkState(this);
        }
    }

    public abstract class State
    {
        public abstract void OutWorkState(Work w);
    }

    public class 上午上班时间 : State
    {
        public override void OutWorkState(Work w)
        {
            if(w.Hour<12)
            {
                Console.WriteLine($"{WorkStatusEnum.上午上班时间.ToString()}");
            }
            else
            {
                w.SetState(new 午休时间());
                w.OutWrokState();
            }
        }
    }


    public class 午休时间 : State
    {
        public override void OutWorkState(Work w)
        {
            if (w.Hour < 13)
            {
                Console.WriteLine($"{WorkStatusEnum.午休时间.ToString()}");
            }
            else
            {
                w.SetState(new 下午上班时间());
                w.OutWrokState();
            }
        }
    }

    public class 下午上班时间:State
    {
        public override void OutWorkState(Work w)
        {
            if (w.Hour < 18)
            {
                Console.WriteLine($"{WorkStatusEnum.下午上班时间.ToString()}");
            }
            else
            {
                w.SetState(new 下班时间());
                w.OutWrokState();
            }
        }
    }


    public class 下班时间 : State
    {
        public override void OutWorkState(Work w)
        {
            if (w.Hour < 22)
            {
                Console.WriteLine($"{WorkStatusEnum.下班时间.ToString()}");
            }
            else
            {
                w.SetState(new 睡觉时间());
                w.OutWrokState();
            }
        }
    }

    public class 睡觉时间 : State
    {
        public override void OutWorkState(Work w)
        {
            if (w.Hour> 22)
            {
                Console.WriteLine($"{WorkStatusEnum.睡觉时间.ToString()}");
            }

        }
    }


}


演示:京东物流


类图:

image.png

源码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 京东物流
{
    /*
    物流系统就很适合使用状态模式来开发,因为此过程存在很多不同的状态,
    例如接单,出库,运输,送货,收货,评价等等。
    而订单在每个不同的状态下的操作可能都不一样,
    例如在接单状态下,商家就需要通知仓库拣货,通知用户等等操作,其他状态类似
    */

    public enum JDStateEnum
    {
        接单,出库,运输,送货,收货,评价
    }

    class Program
    {
        static void Main(string[] args)
        {
            京东物流 jd = new 京东物流();
            jd.StateEnum = JDStateEnum.接单;
            //jd.setStatus(new 接单());
            //jd.doAction();
            jd.setStatus(new 出库());
            jd.doAction();

            Console.ReadKey();
        }
    }


    public class 京东物流
    {
        public JDStateEnum StateEnum
        { get; set; }
    
        private IState state;

        public 京东物流()
        {
            StateEnum = JDStateEnum.接单;
            state = new 接单();
        }

        public void setStatus(IState _state)
        {

            this.state = _state;
        }

        public void doAction()
        {
            state.DoAction(this);
        }

        

    }


    public interface IState
    {
    
        void DoAction(京东物流 state);
    }

    public class 接单 : IState
    {
        public void DoAction(京东物流 state)
        {

            Console.WriteLine($"接单");
            state.StateEnum = JDStateEnum.出库;
            state.setStatus(new 出库());
            state.doAction();

        }

    }

    public class 出库 : IState
    {
        public void DoAction(京东物流 state)
        {
            Console.WriteLine($"出库");
            if (state.StateEnum == JDStateEnum.接单)
            {
                state.StateEnum = JDStateEnum.运输;
                state.setStatus(new 运输());
                state.doAction();
            }
        }
    }

    public class 运输 : IState
    {
      

        public void DoAction(京东物流 state)
        {
            Console.WriteLine($"运输");
            if (state.StateEnum == JDStateEnum.出库)
            {
                state.StateEnum = JDStateEnum.送货;
                state.setStatus(new 送货());
                state.doAction();
            }
        }
    }

    public class 送货 : IState
    {
     
        public void DoAction(京东物流 state)
        {
            state.StateEnum = JDStateEnum.送货;
            Console.WriteLine($"送货");
        }
    }


}


演示:线程生命周期



image.png





在游戏开发中的应用


状态模式在游戏实际开发过程中的应用,一般用于游戏场景转换,角色AI状态管理等。

1、游戏场景转换



说明:

ISceneState:场景类的接口,定义游戏中场景转换和执行时所需要调用的方法。

StartState,MainMenuState, BattleState:分别对应于游戏的开始场景,

主场景,战斗场景,作为场景执行时的操作类。

SceneStateController:场景状态的拥有者,保持当前游戏场景状态,

并作为与GameLoop类互动的接口,也是执行场景转换的类。

GameLoop:游戏主循环类,包含游戏初始化以及定期调用更新操作。


2、角色AI状态管理



说明:

IAIState:角色的AI状态,定义游戏中角色AI操作时所需要的接口

AttackAIState、ChaseAIState、IdleAIState、MoveAIState:角色具体状态类,

负责实现角色在各个状态下应该有的游戏行为和判断,这些状态可以设置给游戏中的双方阵营角色。

ICharacterAI:双方阵营角色的AI接口,定义游戏所需的AI方法,并实现相关的AI操作。

SoldierAI,EnemyAI:ICharacterAI的子类,讲不同的AI行为表现在不同的子类中实现。

实际应用中的优缺点:


优点:

使用状态模式可以清除地了解某个场景状态执行时所需要配合使用的类对象,

并且减少因新增状态而需要大量修改现有程序代码的维护成本。 

简言之,使用状态模式能够减少错误并降低维护难度,并且状态执行环境单一化。


缺点:

如果游戏中有非常多的场景需要进行状态管理,有可能造成类爆炸,导致后期维护难度上升。



--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:
本帖最后由 勇哥,很想停止 于 2023-11-27 17:11:39 编辑

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864