状态者模式

一、概念

状态模式是一种较为复杂的行为型设计模式,它用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统的某个对象存在多个状态时,这些状态可以互相转换,而且对象在不同的状态下行为也不相同。

很多时候状态切换表示会用if-else 或者switch 来表示状态,这会导致代码的可维护性和灵活度下降,当出现新的状态时,需要修改客户端代码,不符合开闭原则。状态模式将对象的状态分离,封装到专门的状态类中,使对象可以灵活变化。

定义:允许一个对象在其内部状态改变时改变其行为

二、模式结构

  • Context(环境类):又称上下文类,它是拥有多种状态的对象,维护一个抽象State的实例,该实例用来定义当前状态。

  • State(抽象状态类):它用户定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态中声明了各种不同状态对应的方法。

  • ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类状态相关的行为。

    image-20240722141655701

三、具体实现

  • Fsm类:环境类
 public abstract class FsmBase
 {
     public string Name { get; protected set; }
     public string FullName { get; protected set; }
     public int FsmStateCount { get; protected set; }
     // 是否在运行
     public bool IsRunning { get; protected set; }
     //是否被销毁
     public bool IsDestory { get; protected set; }

     public FsmState CurrentState { get; protected set; }

     public object Owner;
     public abstract void Start<TState>();

     public abstract bool HasState<TState>();

     public abstract FsmState GetState<TState>();

     public abstract FsmState[] GetAllStates();

     public abstract void ChangeState<TState>();
     public abstract void OnUpdate(float elapseSeconds, float realElapseSeconds);
     public abstract void OnRelease();
 }

public class Fsm : FsmBase
{
    private readonly Dictionary<Type, FsmState> _states;
    public Fsm() 
    {
        CurrentState = null;
        IsRunning = true;
        IsDestory = false;
        _states = new Dictionary<Type, FsmState>();
    }

    public static Fsm Create<T>(string name, T owner, FsmState[] states)
    {
        if (owner == null)
        {
            GameLogger.LogError("Fsm owner is invaild");
            return null;
        }
        if (states == null || states.Length == 0)
        {
            GameLogger.LogError("Fsm states is invaild");
            return null;
        }
        // TODO:对象池获得Fsm状态机
        Fsm fsm = new Fsm();
        fsm.Name = name;
        fsm.Owner = owner;
        fsm.IsDestory = false;
        fsm.IsRunning = true;

        foreach (var state in states)
        {
            if (state == null)
            {
                GameLogger.LogError("Fsm states is invaild");
                return null;
            }
            Type stateType = state.GetType();
            if (fsm._states.ContainsKey(stateType))
            {
                GameLogger.LogError($"Fsm {typeof(T)} state {stateType.FullName} is already exist.");
            }
            state.fsm = fsm;
            fsm._states.Add(stateType, state);
            state.OnInit();
        }
        return fsm;
    }

    public override void ChangeState<TState>()
    {
        if (CurrentState == null)
        {
            GameLogger.LogError("Current state is invaild");
            return;
        }

        FsmState state = GetState<TState>();
        if (state == null)
        {
            GameLogger.LogError($"Fsm {Name} can't change state to {typeof(TState).FullName}");
            return;
        }
        CurrentState.OnExit(false);
        CurrentState = state;
        CurrentState.OnEnter();
    }

    public override FsmState[] GetAllStates()
    {
        int index = 0;
        FsmState[] results = new FsmState[_states.Count];
        foreach (var state in _states)
        {
            results[index ++] = state.Value;
        }
        return results;
    }

    public override FsmState GetState<TState>()
    {
        FsmState state = null;
        if (_states.TryGetValue(typeof(TState), out state))
        {
            return state;
        }
        return null;
    }

    public override bool HasState<TState>()
    {
        return _states.ContainsKey(typeof(TState));
    }

    public override void Start<TState>()
    {
        if (IsRunning)
        {
            GameLogger.LogError("Fsm is Running, can't start again");
            return;
        }
        FsmState state = GetState<TState>();
        if (state == null)
        {
            GameLogger.LogError($"Fsm {Name} can't not start state {typeof(TState).FullName} which is not exist");
            return;
        }

        CurrentState = state;
        CurrentState.OnEnter();
    }

    public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
    {
        if (CurrentState == null) return;

        CurrentState.OnUpdate(elapseSeconds, realElapseSeconds);
    }

    public override void OnRelease()
    {
        if (CurrentState != null)
        {
            CurrentState.OnExit(true);
        }

        foreach (var state in _states)
        {
            state.Value.OnDestory();
        }

        Name = null;
        _states.Clear();
        CurrentState = null;
        IsDestory = true;
        IsRunning = false;
    }
}
  • FsmState类:抽象状态类
public abstract class FsmState
{
    public FsmBase fsm;
    public FsmState()
    {
        fsm = null;
    }

    protected internal virtual void OnInit(){ }
    protected internal virtual void OnEnter() { }
    protected internal virtual void OnUpdate(float elapseSeconds, float realElapseSeconds) { }
    protected internal virtual void OnExit(bool isShutDown) { }
    protected internal virtual void OnDestory() { }

    protected void ChangeState<TState>()
    {
        try
        {
            if (fsm == null)
            {
                GameLogger.LogError("FSM is Invaild");
            }
            fsm.ChangeState<TState>();
        }
        catch
        {
            GameLogger.LogError($"Fsm ChangeState error");
        }

    }
}

四、优缺点

  • 优点:
    1. 结构清晰:状态模式将状态相关的代码组织在一起,符合单一原则。
    2. 分离状态转换逻辑:状态转换的逻辑被封装在状态类中,与上下文对象分离,易于扩展和转换逻辑。
    3. 提高可拓展性和可维护性:对象的状态和行为被分离,使系统更易于扩展和维护。
  • 缺点:
    1. 类的个数增加:如果状态过多会导致类的个数增加,从而增加系统复杂度。
    2. 开闭原则:在判断状态转换时,每一次增加一个状态就要修改状态转换的代码。

五、适用环境

  1. 对象的行为依赖于它的状态,状态的改变将导致行为的改变。
  2. 存在多个状态且可以互相转换。