状态机浅析

2,797 阅读5分钟

什么是状态机?

状态机(State Machine),也称为有限状态机(Finite State Machine, FSM),是一种用来描述一个系统的行为模型。

状态机由一组状态、一组输入事件和一组转换规则组成。系统在任何时刻都处于一种状态,当输入事件发生时,根据当前状态和转换规则,系统可能会转移到另一种状态。

状态机主要包括以下几个部分:

  1. 第一个是 State ,状态。一个状态机至少要包含两个状态。
  2. 第二个是 Event ,事件。事件就是执行某个变换的触发条件。
  3. 第三个是 Transition ,变换。也就是从一个状态变化为另一个状态。
  4. 第四个是 Guard,条件。状态变换需要满足的约束。

状态机广泛应用于软件和硬件设计中,特别是在需要明确不同状态行为和事件处理的场景,如网络协议、游戏开发、用户界面设计等。

通过使用状态机,可以使系统的行为更加清晰,易于理解和维护。

为什么需要状态机?

我们先看看服务派单场景:

image.png

特点:

  1. 有特定的几个状态:初始化、待接单、待派单 ...
  2. 可能存在多种场景流向一个状态:比如【取消】,可以从 初始化、待接单 ... 流转而来
  3. 逻辑分散:各种场景,流转分散在不同的实现中
  4. 随着产品迭代愈加复杂,难以维护。

如何解决这个问题?

  1. 首先,我们可以考虑将状态全部从业务层抽离,进行统一收口维护。
  2. 其次,抽象状态流转规则,比如:A状态转变为B状态,需要在约束C下,并要完成一些 action
  3. 最后,我们将状态流转规则也进行统一收口,这样便能清晰的管理状态和流转规则。

以上几个步骤的层层处理,便是状态机的基本思想。

状态机(State Machine)有较强的实践意义,主要有以下几个特点:

  1. 清晰的状态管理:状态机通过明确定义系统的所有可能状态以及在这些状态之间的转换,帮助开发者清晰地管理和跟踪系统的状态。
  2. 简化复杂逻辑:通过将复杂的条件分支逻辑转换为状态图,使得逻辑更加直观易懂。
  3. 易于维护和扩展:状态机的结构使得对系统的修改和扩展变得更加容易。
  4. 提高可测试性:由于状态机的行为是预定义的,因此可以更系统地进行测试
  5. 有助于团队沟通:状态机提供了一种图形化的方式来描述系统行为,这对于团队成员之间的沟通是非常有帮助的。非技术团队成员也能理解状态图,从而更好地参与项目讨论。

总之,状态机提供了一种结构化和高效的方法来设计和实现那些需要精确控制状态和事件的系统。

通过使用状态机,开发者可以更容易地构建出可靠、易维护且高效的系统。

常见开源状态机

有许多开源的状态机库可以帮助开发者更容易地实现和管理状态机。这些库通常提供了丰富的功能和灵活的配置选项,适用于各种编程语言和应用场景。以下是一些流行的开源状态机库:

  1. Spring State Machine

  2. Squirrel

    • 一个轻量级且易于使用的Java状态机框架,支持状态、转换、条件和动作的灵活配置。
    • Squirrel GitHub

如何实现状态机

Spring State Machine 是一款常见的状态机,我们先看看如何使用:

1、定义状态

public enum StatesEnum {

    S0, S1, S2, S3

}

2、定义事件

public enum EventsEnum {

    E1, E2

}

3、定义状态流转配置

@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<StatesEnum, EventsEnum> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<StatesEnum, EventsEnum> config) throws Exception {
        config.withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<StatesEnum, EventsEnum> states) throws Exception {
        states
                .withStates()
                .initial(StatesEnum.S0)
                .states(EnumSet.allOf(StatesEnum.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<StatesEnum, EventsEnum> transitions) throws Exception {
        transitions
                .withExternal()
                .source(StatesEnum.S0).target(StatesEnum.S1).event(EventsEnum.E1).action((context) -> {
                    System.out.println("action");
                })
                .and()
                .withExternal()
                .source(StatesEnum.S1).target(StatesEnum.S2).event(EventsEnum.E2);
    }

    @Bean
    public StateMachineListener<StatesEnum, EventsEnum> listener() {
        return new StateMachineListenerAdapter<StatesEnum, EventsEnum>() {
            @Override
            public void stateChanged(State<StatesEnum, EventsEnum> from, State<StatesEnum, EventsEnum> to) {
                System.out.println("State change to " + to.getId());
            }
        };
    }
}

状态、流转规则等统一收口到 Config 进行处理,逻辑十分清晰、便于维护。

Spring State Machine 功能已经非常完善了,但存在一些缺点:

  1. 笨重:功能很完善,但大部分都用不上
  2. 有状态:有状态性也就意味着不能通用一个状态机,需要为每个实例定义状态机。而大多数情况下,我们可能只需要逻辑上的状态机来控制状态流转关系。

如果你仅需要简单的无状态的状态机,完全可以自己简单实现:可以通过简单的枚举实现、简单的状态/流转规则收口。

当然,也可以参考 Spring State Machine 更加规范化的实现状态机,在实现时考虑几个点:

  1. 第一个是 State ,状态。一个状态机至少要包含两个状态。
  2. 第二个是 Event ,事件。事件就是执行某个变换的触发条件。
  3. 第三个是 Transition ,变换。也就是从一个状态变化为另一个状态。
  4. 第四个是 Guard,条件。状态变换需要满足的约束。

具体实现也比较简单,自己动手试试吧~