常见设计模式_状态模式

本文详细解释了如何在面向对象编程中使用状态模式,通过状态划分和有限状态机结构实现对象行为的变化,强调了状态管理类的抽象和泛型应用,以及在多状态场景下的优化。同时讨论了状态模式的优缺点和适用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

思想:

        对象内部状态改变时改变自身的行为

核心:

        对对象的操作进行状态划分,每个状态都关联着一组输入。(读者需要了解的是思想,而不是代码,代码只是为了让读者更方便了解这一模式。这是最重要的!!!)

实现方式:

        有限状态机:

        以下是状态机比较通用的结构。可以满足大多数需求

        状态机简易版

public enum UserState   ///状态的Enum,划分状态类型
{
    None,
    MoveObj,
    SelectObj
}

public abstract class StateBase //状态基类
{
    //目标,让状态类知道自己服务谁,方便调用目标的资源,比如User中的Animation
    protected User _targetUser;
    public virtual void OnStart() { }   //进入状态
    public virtual void OnUpdate() { }  //每帧更新
    public virtual bool JudgeCanExit() { return true; }  //能否退出
    public virtual void OnExit() { }    //退出
}

/// 实际状态类,与UserState中一一对应
public class MoveState : StateBase
{
    public MoveState(User _user) 
    {
        this._targetUser = _user;
    }
}

public class SelectState : StateBase
{
    public SelectState(User _user)
    {
        this._targetUser = _user;
    }
}

public class StateMgr //状态管理类
{
    private Dictionary<UserState, StateBase> _stateDic; //状态字典
    private UserState _currentStateEnum;    //当前状态Enum
    private StateBase _currentState;        //当前状态
    public void Init(User _target)     //状态初始化
    {
        _stateDic = new Dictionary<UserState, StateBase>();
        _stateDic.Add(UserState.MoveObj, new MoveState(_target));
        _stateDic.Add(UserState.SelectObj, new SelectState(_target));
    }
    public void OnUpdate()     //更新脚本
    {
        if(_currentState != null)
            _currentState.OnUpdate();
    }
    public void ChangeState(UserState _state)     //切换状态
    {
        if (_state == _currentStateEnum)
            return;
        if (OnExitState())
        {
            _currentStateEnum = _state;
            _currentState = _stateDic[_currentStateEnum];
            _currentState.OnStart();
        }
    }
    public bool OnExitState() //退出当前
    {
        if (_currentStateEnum != UserState.None)
        {
            if (!_currentState.JudgeCanExit())
            {
                return false;
            }
            else
            {
                _currentState.OnExit();
                _currentStateEnum = UserState.None;
                _currentState = null;
                return true;
            }
        }
        return true;
    }
}

public class User //状态目标类
{
    private StateMgr _stateMgt;   //状态管理类 
    public Animation _animation;

    public User()
    {
        _stateMgt = new StateMgr();
        _stateMgt.Init(this);
    }

    private void Update() 
    {
        _stateMgt.OnUpdate();
    }
}

再次抽象

        状态机简易版代码中,存在很多可以优化的方案,可以试想一下以下情形:项目中同时存在角色状态UserState,以及角色属性状态PropertyState。此时这套程序就需要设计为UserStateMgr(用户状态管理)以及PropertyStateMgr(属性状态管理)、以及两套的Enum。这显然可以进一步优化

        该部分脚本较长,读者可先查看下方知识点讲解后再阅读代码,在此之前,读者需要理解上一版的脚本,因为知识点讲解很多是对比上一版进行讲解

public enum UserState   ///状态的Enum,划分状态类型
{
    None,
    MoveObj,
    SelectObj
}

public abstract class StateBase<T> //状态基类
{
    protected T _target;    //目标
    public virtual void OnStart() { }   //进入状态
    public virtual void OnUpdate() { }  //每帧更新
    public virtual bool JudgeCanExit() { return true; }  //能否退出
    public virtual void OnExit() { }    //退出
}

/// 实际状态类,与UserState中一一对应
public class MoveState : StateBase<User>
{
    public MoveState(User _user) 
    {
        this._target = _user;
    }
}

public class SelectState : StateBase<User>
{
    public SelectState(User _user)
    {
        this._target = _user;
    }
}

public abstract class StateMgr<T> //状态管理类
{
    protected T _target;
    protected Dictionary<int, StateBase<T>> _stateDic; //状态字典
    private int _currentStateEnum;    //当前状态Enum
    private StateBase<T> _currentState;        //当前状态
    public void Init(T _target) 
    {
        this._target = _target;
        _stateDic = new Dictionary<int, StateBase<T>>();
    }
    public void OnUpdate() 
    {
        if(_currentState != null)
            _currentState.OnUpdate();
    }
    public void ChangeState(int _state) 
    {
        if (_state == _currentStateEnum)
            return;
        if (OnExitState())
        {
            _currentStateEnum = _state;
            if (!_stateDic.TryGetValue(_state, out _currentState))
                _stateDic.Add(_state, CreateState(_state));
            _currentState = _stateDic[_currentStateEnum];
            _currentState.OnStart();
        }
    }
    public bool OnExitState() 
    {
        if (_currentStateEnum != 0)
        {
            if (!_currentState.JudgeCanExit())
            {
                return false;
            }
            else
            {
                _currentState.OnExit();
                _currentStateEnum = 0;
                _currentState = null;
                return true;
            }
        }
        return true;
    }

    protected virtual StateBase<T> CreateState(int _stateIndex) 
    {
        return null;
    }
}

public class UserStateMgr :StateMgr<User>
{
    protected override StateBase<User> CreateState(int _stateIndex)
    {
        switch (_stateIndex)
        {
            case 0:
                return null;
            case 1:
                return new MoveState(_target);
            case 2:
                return new SelectState(_target);
        }
        return null;
    }
}

public class User //用户类
{
    private StateMgr<User> _stateMgt;
    public Animation _animation;

    public User()
    {
        _stateMgt = new UserStateMgr();
        _stateMgt.Init(this);
    }

    private void Update() 
    {
        _stateMgt.OnUpdate();
    }
}
知识点:

        1.为何给状态基类StateBase改为泛型StateBase<T>

                为了抽象出具体状态的操作对象_target,如果不这么写那么对于操作对象(脚本中的User类需要向上抽象出一层Target类放在StateBase类中作为_target。或者删除StateBase中的_target,再在具体类中命名属性,这两种办法无疑很浪费资源,所以最优解是改为泛型)。

        2.为何将StateMgr(状态管理)类进行抽象

        在上一版脚本中,我们初始化状态时在Init中添加了不同的状态。但那是针对只有一类状态的情况,此时我们有多个类的状态。程序不知道该添加哪些具体的状态,所以对StateMgr进行抽象,然后添加CreateState获取该管理类中具体的状态。

        3.状态字典为什么使用int类型,而不是enum

        对于标记类型来说,enum无疑是最正确的方法,但这么做有两个原因

        (1)enum做字典的Key,使用时会对enum进行装箱处理。

        (2)以免在StateMgr(状态管理类中)再添加UserState(Enum)的泛型

        4.为何在StateMgr(状态管理)类中添加泛型目标_target

        对于状态管理类来说,是不需要目标这一概念的,但在创建实际状态的时候,也就是CreateState中,需要对状态赋值_target,所以添加泛型目标。

优缺点:

        优点:对用户进行分层

        1.回使复杂逻辑变得简单

        2.可以隔离特殊属性:比如移动对象类,存在移动速度属性,如果不划分对象,选择对象类是可以读取到移动速度属性。

        缺点:过多的状态会使类过于分散。

没有完美的设计模式,开发者需要自行判断使用环境。

下一篇会将状态模式变体的实现思想,比如下推状态机等。这篇内容涉及到的知识比较多,读者如果又看不懂可以私信作者,有时间的话会对读者的问题一一作答。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值