现有一个任务在MMORPG(Game)中控制玩家(Player)的状态,现不从总体上去考虑使用状态模式。我一步步引出。我先写在同一个方法里面,因为传统的做法都是这样的。

首先Player的状态初始化为站立状态(stand_state) , 如果玩家按“J”键的话,玩家开始跳跃:

        public void Palyer_Handler( string type = "J" )
        {
            switch(type)
            {
                case "J":
                    Console.WriteLine("Player Jump");
                    //to do jumping code
                    break;
            }
        }

这段代码肯定是有问题的 , 因为player还在空中 , 如果再输入“J”, Player会再Jump一次,我们释放type为~(此动作做完后发送)所有动作都用这一套路,这显然是不正确的。那好我们队上述代码进行一次修改:

        private bool is_jumping = false;
        public void Palyer_Handler( string type = "J" )
        {
            switch(type)
            {
                case "J":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Jump");
                        //to do jumping code
                    }
                    break;
                default:
                    if( type == "~" )
                    {
                        is_jumping = false;
                    }
                    break;
            }
        }

上述代码的意思很简单 : 当Player不是出于jumping状态时 , 如果键入“J”则让其jump。否则不做处理。


好了, Player需要扩展动作 :走(walk) , 现定义键入“W”(按着不松 ,释放W自动返回站立stand状态),玩家walk,代码如下:

        private bool is_jumping = false;
        public void Palyer_Handler( string type = "J" )
        {
            switch(type)
            {
                case "J":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Jump");
                        //to do jumping code
                        is_jumping = false;
                    }
                    break;
                case "W":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Walk");
                        //to do walking code
                    }
                    break;
                default:
                    if( type == "~" )
                    {
                        is_jumping = false;
                    }
                    break;
            }
        }

如上述code , 加了一个walk状态 , 那么is_jumping将要做很多的状态处理。此时不是特别麻烦

我们加一个跑的状态(run), type== “R”进入此状态 

        private bool is_jumping = false;
        public void Palyer_Handler( string type = "J" )
        {
            switch(type)
            {
                case "J":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Jump");
                        //to do jumping code
                        is_jumping = false;
                    }
                    break;
                case "W":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Walk");
                        //to do walking code
                    }
                    break;
                case "R":
                    Console.WriteLine("Player Run");
                    //to do runing code 
                    break;
                default:
                    if( type == "~" )
                    {
                        is_jumping = false;
                    }
                    break;
            }
        }

我这样写肯定是错的

jump状态的时候 , 肯定是不能进入run状态 , 但是walk状态的时候绝对可以进入run状态。有人的想法是:

        private bool is_jumping = false;
        public void Palyer_Handler( string type = "J" )
        {
            switch(type)
            {
                case "J":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Jump");
                        //to do jumping code
                        is_jumping = false;
                    }
                    break;
                case "W":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Walk");
                        //to do walking code
                    }
                    break;
                case "R":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Run");
                        //to do runing code 
                    }
                    break;
                default:
                    if( type == "~" )
                    {
                        is_jumping = false;
                    }
                    break;
            }
        }

当walk状态结束回到stand状态后 , 可以进入run状态。ok ??? 好吧 , 也许策划伙伴们足够宽容,允许你先从走到 站一会 再跑 。


我们再加一个动作俯冲(subduction)以快速结束jump(可能加一个道具啊), 只有在jumping的状态中,才能执行subduction。type==“S”俯冲

        private bool is_jumping = false;
        public void Palyer_Handler( string type = "J" )
        {
            switch(type)
            {
                case "J":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Jump");
                        //to do jumping code
                    }
                    break;
                case "W":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Walk");
                        //to do walking code
                    }
                    break;
                case "R":
                    if (!is_jumping)
                    {
                        is_jumping = true;
                        Console.WriteLine("Player Run");
                        //to do runing code 
                    }
                    break;
                case "S":
                    if(is_jumping)
                    {
                        is_jumping = false;
                        Console.WriteLine("Player Subduction");
                        // to do subduction code
                    }
                    break;
                default:
                    if( type == "~" )
                    {
                        is_jumping = false;
                    }
                    break;
            }
        }


至此代码彻底乱了 , 没法再坐下去了 , 如果再加一个卧倒 , 捡道具的动作,估计开发人员会哭。我们可以把这些状态画出来:

状态模式_第1张图片

我们拥有一组状态 , 这些状态之间可以互相切换 。同一时刻只能处理一种状态的行为。这样的情况我们可以使用状态模式。

现在用C#实现上图结构的状态模式:

一 : 定义一个接口IState , 上述5个状态都都实现这一接口

namespace StatePattern_Demo
{
    public interface IState
    {
        /// 
        /// 处理行为
        /// 
        /// 
        void Handle(Context context);
        /// 
        /// 切换状态
        /// 
        /// 
        /// 是否自动执行切换后动作
        /// 是否切换成功
        Boolean SwitchState(IState state , Boolean is_play = false);
    }
}

跑状态继承IState

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StatePattern_Demo
{
    /// 
    /// 跑
    /// 
    public sealed class Run_State : IState
    {
        private Context _context = null;
        public void Handle(Context context)
        {
            this._context = context;
            Console.WriteLine("正在处理Run事件。。。。。。。");
            //用于模拟run的结束时间
            System.Timers.Timer mytimer = new System.Timers.Timer(5000);//实例化Timer类
            mytimer.Elapsed += new System.Timers.ElapsedEventHandler(aotoSwicth);//定义定时到达时执行的事件;
            mytimer.AutoReset = false;//设置是否自动重启,即自动执行多次;
            mytimer.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件;
        }
        private void aotoSwicth(object source, System.Timers.ElapsedEventArgs e)
        {
            if (this._context.State != this) return;//如果state已经改变不做任何处理
            //回到站立状态
            this.SwitchState(new Stand_State(), true);
        }
        public Boolean SwitchState(IState state, Boolean is_play = false)
        {
            Type target_state_type = state.GetType();
            if (target_state_type == typeof(Walk_State) || target_state_type == typeof(Stand_State))
            {
                //在"跑"状态可以切换至"走"状态或者“站立”状态
                this._context.State = state;
                if( is_play )
                {
                    this._context.Play();//立即执行
                }
                return true;
            }
            return false;
        }
    }
}

走状态继承IState:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StatePattern_Demo
{
    public sealed class Walk_State : IState
    {
        private Context _context = null;
        public void Handle(Context context)
        {
            this._context = context;
            Console.WriteLine("正在处理Walk事件。。。。。。。");
            
            System.Timers.Timer mytimer = new System.Timers.Timer(5000);//实例化Timer类
            mytimer.Elapsed+=new System.Timers.ElapsedEventHandler(aotoSwicth);//定义定时到达时执行的事件;
            mytimer.AutoReset = false;//设置是否自动重启,即自动执行多次;
            mytimer.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件;
        }
        private void aotoSwicth(object source, System.Timers.ElapsedEventArgs e)
        {
            if (this._context.State != this) return;//如果state已经改变不做任何处理
            //回到站立状态
            this.SwitchState(new Stand_State(), true);
        }
        public Boolean SwitchState(IState state, Boolean is_play = false)
        {
            Type target_state_type = state.GetType();
            if (target_state_type == typeof(Run_State) || target_state_type == typeof(Stand_State))
            {
                //在“走”状态可切换至“跑”状态或者站立状态
                this._context.State = state;
                if( is_play )
                {
                    this._context.Play();
                }
                return true;
            }
            return false;
        }
    }
}

站立状态继承IState:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StatePattern_Demo
{
    /// 
    /// 站立
    /// 
    public sealed class Stand_State : IState
    {
        private Context _context = null;
        public void Handle(Context context)
        {
            this._context = context;
            Console.WriteLine("持续Standing。。。。。。");
        }
        public Boolean SwitchState(IState state, Boolean is_play = false)
        {
            Type target_state_type = state.GetType();
            if( target_state_type == typeof(Subduction_State) )
            {
                return false;//在站立状态不能俯冲
            }
            else
            {
                this._context.State = state;
                if (is_play)
                {
                    this._context.Play();
                }
                return true;
            }
            return false;
        }
    }
}

跳跃状态继承IState:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace StatePattern_Demo
{
    /// 
    /// 跳跃
    /// 
    public sealed class Jump_State : IState
    {
        private Context _context = null;
        public void Handle(Context context)
        {
            this._context = context;
            Console.WriteLine("正在处理Jump事件。。。。。。。");
            System.Timers.Timer mytimer = new System.Timers.Timer(5000);//实例化Timer类
            mytimer.Elapsed += new System.Timers.ElapsedEventHandler(aotoSwicth);//定义定时到达时执行的事件;
            mytimer.AutoReset = false;//设置是否自动重启,即自动执行多次;
            mytimer.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件;
        }
        private void aotoSwicth(object source, System.Timers.ElapsedEventArgs e)
        {
            if (this._context.State != this) return;//如果state已经改变不做任何处理
            //回到站立状态
            this.SwitchState(new Stand_State(), true);
        }
        public Boolean SwitchState(IState state , Boolean is_play = false)
        {
            Type target_state_type = state.GetType();
            if (target_state_type == typeof(Subduction_State) || target_state_type == typeof(Stand_State))
            {
                //在“跳跃”状态中可以执行“俯冲”动作 或者 “站立”动作
                this._context.State = state;
                if( is_play )
                {
                    this._context.Play();
                }
                return true;
            }
            return false;
        }
    }
}

俯冲状态继承IState:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StatePattern_Demo
{
    /// 
    /// 俯冲
    /// 
    public sealed class Subduction_State : IState
    {
        private Context _context = null;
        private Boolean is_play_over = false;
        public void Handle(Context context)
        {
            this._context = context;
            is_play_over = false;
            Console.WriteLine("正在处理Subduction事件。。。。。。。");

            System.Timers.Timer mytimer = new System.Timers.Timer(5000);//实例化Timer类
            mytimer.Elapsed += new System.Timers.ElapsedEventHandler(aotoSwicth);//定义定时到达时执行的事件;
            mytimer.AutoReset = false;//设置是否自动重启,即自动执行多次;
            mytimer.Enabled = true;//是否执行System.Timers.Timer.Elapsed事件;
        }
        private void aotoSwicth(object source, System.Timers.ElapsedEventArgs e)
        {
            if (this._context.State != this) return;//如果state已经改变不做任何处理
            //回到站立状态
            is_play_over = true;//俯冲动作已经完成
            this.SwitchState(new Stand_State(), true);
        }
        public Boolean SwitchState(IState state , Boolean is_play = false)
        {
            if ( is_play && is_play_over )
            {
                Type target_state_type = state.GetType();
                if( target_state_type == typeof(Stand_State) )
                {
                    this._context.State = state;
                    this._context.Play();//立即执行
                    return true;
                }
            }
            return false;
        }
    }
}

二 : 定义一个Context类来保存当前的IState

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StatePattern_Demo
{
    public sealed class Context
    {
        private IState state;
        /// 
        /// 定义Context的初始状态
        /// 
        /// 
        public Context(IState state)
        {
            this.state = state;
        }
        /// 
        /// 可读写的状态属性,用于读取和设置新状态
        /// 
        public IState State
        {
            get { return state; }
            set { state = value; }
        }
        /// 
        /// 播放状态的动作
        /// 
        public void Play()
        {
            state.Handle(this);
        }
    }
}

测试一下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StatePattern_Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Context cur_context = new Context(new Stand_State());
            cur_context.Play();
            if( cur_context.State.SwitchState(new Walk_State()) )
            {
                cur_context.Play();
            }
            Thread.Sleep(1000);
            cur_context.State.SwitchState(new Run_State(), true);
            if (!cur_context.State.SwitchState(new Subduction_State()))
            {
                Console.WriteLine("Run 状态无法俯冲");
            }
            Thread.Sleep(5100);
            if( cur_context.State.SwitchState(new Jump_State()) )
            {
                cur_context.Play();
            }
            cur_context.State.SwitchState(new Subduction_State(), true);
            Console.ReadKey();
        }
    }
}

结果:

状态模式_第2张图片

好了 , 完全符合我们的期待 , 把状态的控制交代到一个个类里面 , 把总体复杂的状态判断分解到相对简单的单独的一个一个的判断。