有限状态机

9,163 阅读4分钟

是什么

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移动作等行为的数学模型

状态机有四个核心概念,这是所有状态机的基础

  • State:状态。一个状态机至少要包含两个状态。
  • Transition:过渡。也就是从一个状态变化为另一个状态(即状态转移)。
  • Event:事件。事件会触发状态转移,也就是状态转移的条件。
  • Action:动作。事件发生以后要执行动作,即事件处理

有限状态机的工作原理如图所示,发生事件(event)后,根据当前状态(cur_state) ,决定执行的动作(action),并设置下一个状态(next_state)。

image-20220515152906984.png

我的理解

状态机就是根据一个对象的状态转换规则建立起来的一个数学模型,这个模型描述了一个对象的所有状态以及这些状态之间的转换关系。

我理解该模型中有如下元素:

  1. 对象的所有状态
  2. 状态转移
  3. 触发状态转移的事件
  4. 状态转换后要执行的动作

有什么用、为什么要用

状态转换包含哪些问题?

  1. 如何转换:一个状态要如何转换到另一个状态
  2. 转换后要干啥:状态转换之后要触发什么动作/操作,一般利用事件发布和处理机制,状态转移时发出事件,然后进行事件处理。

状态机可以很好的解决这些问题,特别是当一个对象的状态非常复杂时,使用状态机来管理就可以很清晰地知道状态之间如何转换以及如何处理状态转换。

image-20220515154724853.png

  • 业务中涉及到一些关于状态的操作,常见的就是订单,每个订单都会有自己的状态,订单的一些行为受限于当前订单的状态,订单的状态直接用常量表示,业务进行前的检查部分通过if判断来检测当前单据是否可以流转到目标状态,逻辑里充满大量判断,违背了设计原则
  • 业务发展的比较快,某些订单状态不停的增加,每一次增加都需要改动业务中使用到状态的相关代码,更糟的的是这些代码可能遍布于多个类的多个方法中,不仅增加发布的风险也同时增加了测试难度。
  • 状态及状态转换与业务解耦

总结:代码高耦合、低内聚、不易扩展,可维护性差,可测试性差,代码不易理解,用状态机重构

通过应用状态机的方式来优化业务系统,将所有的状态、事件、动作都抽离出来,对复杂的状态迁移逻辑进行统一管理,来取代冗长的 if else 判断,使系统中的复杂问题得以解耦,变得直观、方便操作,使系统更加易于维护和管理。

用状态机来管理对象生命流的好处更多体现在代码的可维护性、可测试性上,明确的状态条件、原子的响应动作、事件驱动迁移目标状态,对于流程复杂易变的业务场景能大大减轻维护和测试的难度。

具体示例

举个例子,图书馆借书,流程图如下:

image-20220515152354020.png

根据上图,我们有了:

  1. 定义了可能的输入的动作(借出、归还、损坏、丢失、修复和查找)
  2. 有限数量的状态(库存、借出、损坏和丢失)
  3. 初始状态(库存)

将上述状态翻译成代码:

{
  "initial": "In Stock",   #初始状态
  "states": {
    "In Stock": {         #库存状态只有 “借出” 动作
      on: {
        "Lend": "Lent",
      },
    },
    "Lent": {             #借出状态有 “return”,“damage”,“lose”三个动作,并且对应过渡之后的状态
      on: {
        "Return": "In Stock",
        "Damage": "Damaged",
        "Lose": "Lost",
      },
    },
    "Damaged": {
      on: {
        "Repair": "In Stock",
      },
    },
    "Lost": {
      on: {
        "Find": "In Stock",
      },
    },
  },
}

使用easy-states状态机代码示例:

        // 状态
	State stock = new State(BookState.STOCK.name());
        State lent = new State(BookState.LENT.name());
        State lost = new State(BookState.LOST.name());

        Set<State> states = new HashSet<>();
        states.add(stock);
        states.add(lent);
        states.add(lost);
        
		// 过渡
        Transition lentTransition = new TransitionBuilder()
                .name("lent")
                .sourceState(stock)
                .eventType(LentEvent.class) // 事件,即触发这个状态转移的事件
                .eventHandler(new Lend()) // 动作,即事件处理
                .targetState(lent)
                .build();


        Transition returnTransiton = new TransitionBuilder()
                .name("return")
                .sourceState(lent)
                .eventType(ReturnEvent.class)
                .eventHandler(new ReturnBooke())
                .targetState(stock)
                .build();

        FiniteStateMachine fsm = new FiniteStateMachineBuilder(states, stock)
                .registerTransition(lentTransition)
                .registerTransition(returnTransiton)
                .registerFinalState(lost)
                .build();

        fsm.fire(new LentEvent());// 触发事件,会根据当前状态和触发的事件得到目标状态和要执行的动作(即事件处理)
        fsm.fire(new ReturnEvent());// 触发事件
    }

    static class LentEvent extends AbstractEvent { }

    static class ReturnEvent extends AbstractEvent { }


    static class Lend implements EventHandler<LentEvent>{

        @Override
        public void handleEvent(LentEvent event) throws Exception {
            System.out.println("借出去了");
        }
    }

    static class ReturnBooke implements EventHandler<ReturnEvent>{

        @Override
        public void handleEvent(ReturnEvent event) throws Exception {
            System.out.println("还回去了");
        }
    }

博客

juejin.cn/post/684490…