Skip to content

状态模式 ​

小的以前一直以为就是状态机😇.

实际上感觉像是把一个状态单独进行封装, 这样对于每个状态内部的行为可以比较方便的进行管理.

不使用该模式的朴素写法 ​

(长二级标题,可恶)

假设一个简单的场景 —— PlayerController, 有 Hop, Step, Jump 三种状态(捏他怪捏他《守护甜心》)

那么可以朴素的写成

C++
enum class State
{
    Hop,
    Step,
    Jump
};

class Player
{
  public:
	void Player::Update(float dt)
	{
	    switch (state_)
	    {
	    case State::Hop:
	        DoHop(dt);
	
	        if (input_ == Input::Step)
	        {
	            state_ = State::Step;
	        }
	        break;
	
	    case State::Step:
	        DoStep(dt);
	
	        if (input_ == Input::Jump)
	        {
	            state_ = State::Jump;
	        }
	        break;
	
	    case State::Jump:
	        UpdateAirMovement(dt);
	
	        if (IsGrounded())
	        {
	            state_ = State::Step;
	        }
	        break;
	    }
	}

  private:
    State state_ = State::Hop;
};

由于 Hop, Step, Jump 之间到底怎么切换比较神秘,于是随便敲点进去(

这样看上去有点💩,因为比如一个 State 中的行为比较复杂,然后我们把行为和转移条件写成面条. 并且更致命的是如果后面加上其他状态,比如 Draw, Stew 等,那么这个 switch 语句就会快速膨胀,让意大利面条愈发意大利面条.

当然,这边只是Update,如果其中还要加上动画切换等系统的参数更新(和当前State设计相关的系统),那么 switch ... 的结构会大量重复,导致代码又臭又长.(不过实际上这边也会分别定义不同的状态机实现就是,蛮说蛮看)

于是为了好看一点,简化一些,同时让每一个 State 比较好维护, 单独把每一个 State 抽象成一个类,就有点像是「状态模式」的写法了

简单例子 ​

定义一个 State 抽象接口(一般会实现Enter, Update, Exit来进行生命周期的定义),让 HopState, JumpState 等都实现它即可,a,可恶,之前状态机一直是这么写的导致自己一直把他们混在一起,可恶.

然后使用一个 Context 来维护一个当前状态对象的实例, 并将与状态相关的操作委托给当前状态对象执行(说起来比较绕) —— 换言之,类似委托,Context(其实实际上可能并不需要专门写一个这样的类出来),记录当前的 State, 然后需要 Update 等之类的操作,委托给自己记录的 State 对象来进行具体实现(包括当前 State 的行为和 Transition)

那么到上面的例子 ——

C++
class State
{
  public:
    virtual ~State() = default;

    virtual void Enter(Player&) {}
    virtual void Update(Player& player, float dt) = 0;
    virtual void Exit(Player&) {}
};

class Player
{
  public:
    void Update(float dt)
    {
        _state->Update(*this, dt);

        if (_next_state)
        {
            ChangeState(std::move(_next_state));
        }
    }

    void RequestStateChange(std::unique_ptr<State> next)
    {
        _next_state = std::move(next);
    }

    // Context 提供的能力
    void DoHop(float dt);
    void DoStep(float dt);
    void UpdateAirMovement(float dt);

    bool WantStep() const;
    bool WantJump() const;
    bool IsGrounded() const;

  private:
	void ChangeState(std::unique_ptr<State> next)
	{
		if (_state)
		{
			_state->Exit(*this);
		}
		_state = std::move(next);
		if (_state)
		{
			_state->Enter(*this);
		}
	}

  private:
	std::unique_ptr<State> _state;
    std::unique_ptr<State> _next_state;
};

然后在具体的 State 中进行接口的实现 ——

C++
class HopState final : public State
{
  public:
    void Update(Player& player, float dt) override;
};

void HopState::Update(Player& player, float dt)
{
    // 当前 State 的行为
    player.DoHop(dt);

    // 当前 State 的 Transition
    if (player.WantStep())
    {
        player.RequestStateChange(
            std::make_unique<StepState>()
        );
        return;
    }

    if (player.WantJump())
    {
        player.RequestStateChange(
            std::make_unique<JumpState>()
        );
    }
}

假设我们需要什么 IsOnGround 等“能力” —— 直接从传入的 player 身上调取即可,此时的 player 就相当于 Context

这里没有直接在 HopState::Update() 中替换当前 _state,而是先 Request,等当前 State 的 Update 返回之后再真正切换,避免在成员函数执行期间把当前 State 自己析构掉. 诶 生命周期是这样的

缺点 ​

首先我们必须认识到,这种设计模式并没有减少什么复杂度,我们只是把同样的一个东西换一种组织形式(把意大利面分碗来装) —— 让每个 State 的可读性比较高.不过本身的 Transition 的 Guard/Trigger 等依旧没有改变,该写 If 还得写(

不过这样首先的一个问题就是类会不断变多 —— 随着 State 变多, 我们需要实现接口,导致类的数量膨胀,那么相比一个简单的 enum + switch,拆成一大堆 class 大抵会起反作用罢(

其次,由于 Transition 被分到每一个 State 进行管理了,导致我们查找 Transition 逻辑可能会比较复杂一些

似乎看起来如果我们要添加某个 State, 只需要非常简单的新增加一个 State 的具体实现即可,然后万事大吉 —— 十分的开闭原则优化口牙,但是如果需要对 State Interface 进行修改? 如果某个 Transition 涉及到很多个 State? 那就需要更改比较多的旧代码了(因为每个 Transition 都是由 State 自己管理),很坏

状态机 ​

感觉 State Pattern 可以作为实现 FSM 的一种常见 OOP 组织方式, 而朴素面条代码也是一种实现方式.

可以看看 Boost.Statechart 的实现,不赖,它把 Event / Transition / Guard / Hierarchical State 等状态机概念作为 Framework 实现,确乎不错.

然后这叫做 —— “有限状态机”,诶是图诶?! —— 那么是否可以使用表格来进行组织? —— 答案是显然的,如果我们可以把 Transition 也当成数据,然后维护一张 Transition Table (状态转移表) 来描述状态机,也是一种很有意思的想法,继续挖坑🪏💦

总结 ​

状态模式的本质大抵以 State 作为核心而单独打包,使得业务代码写起来尽可能的简单,然后把一些麻烦事委托给 State 的具体实现罢

Released under the MIT License.