您的位置:  首页 > 技术 > java语言 > 正文

有限状态机(FSM)java实现

2022-02-10 18:00 https://my.oschina.net/u/2378709/blog/5438255 TopDuang 次阅读 条评论

有限状态机(FSM)java实现

1. 有限状态机介绍

有限状态机,也称为FSM(Finite State Machine),其在任意时刻都处于有限状态集合中的某一状态。当其获得一个输入字符时,将从当前状态转换到另一个状态,或者仍然保持在当前状态。有限状态机成立的必要条件有:

  1. 对象有一组互斥的状态(或对象的生命周期),且这组状态可以涵盖对象的创建到消亡的整个过程。
  2. 当向对象传入一个信号(或事件)时,对象的状态能从当前状态转换成另一种状态,或者保持状态不变。
  3. 状态是有限的。

如图例所示,红绿灯在同一时间只能亮一个颜色,控制程序可以定义3种不同的事件,每个事件定义好红绿灯的起始颜色和目标颜色,我们不需要直接去操作红绿灯开关,只需要按照一定的顺序发送事件过去,我们就可以精确控制红绿灯的工作,红绿灯的工作控制其实就是一个标准的FSM。

由上所述,FSM一般需要以下4个部分组成:

  1. 对象状态枚举。
  2. 对象事件枚举,并指定事件的起始状态和目标状态。
  3. 事件逻辑体,用于处理状态变更引起的业务逻辑。
  4. 事件注册工厂类,FSM的唯一入口。

2. 从需求开始分析FSM

当我们拿到一个类似需求时应该怎么入手呢?下面我们以一个简单的商城订单的FSM实现为例,从需求分析到代码实现为大家讲解:

首先我们应该从需求中提炼出一个订单具体需要经过哪些状态(这里我只列举了一个简单订单的正向状态)。

订单状态:待支付、待发货、待收货、已取消、已完成

列举出所有状态之后,在作图工具中把状态全部画出来,每个状态进行分析是否能转换为其他状态,如分析待支付状态,用户可以从待支付状态进行付款事件,待支付状态将会转换为待发货,用户也可以从待支付状态取消支付,待支付将会转换为订单取消状态。按照这个思路使用单向箭头将所有事件列举出来,并给每个事件起名字。

订单事件:下单事件、支付事件、发货事件、支付取消事件、收货事件

最后形成如下图所示的状态流转图

3. FSM的java实现

按照上面所说,我们将FSM的4个部分声明出来:

3.1. 对象状态枚举类

使用枚举的方式穷举出订单所有可能的状态。

public enum OrderStatusEnum {
    Unpaid("Unpaid", "待支付"),
    UnShipping("UnShipping", "待发货"),
    UnReceiving("UnReceiving", "待收货"),

    Canceled("Canceled", "已取消"),
    Finished("Finished", "已完成");

    private final String code;
    private final String desc;

    OrderStatusEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

3.2. 对象事件枚举类

为了省事,我在这里把事件和状态做了关联,也可以单独拿出来做一个配置封装。

public enum OrderEventEnum {
    CreateOrder("CreateOrder", "下单事件", null, OrderStatusEnum.Unpaid),
    Payment("Payment", "支付事件", OrderStatusEnum.Unpaid, OrderStatusEnum.UnShipping),
    Shipping("Shipping", "发货事件", OrderStatusEnum.UnShipping, OrderStatusEnum.UnReceiving),
    Receiving("Receiving", "收货事件", OrderStatusEnum.UnReceiving, OrderStatusEnum.Finished),
    CancelPayment("CancelPayment", "支付取消事件", OrderStatusEnum.Unpaid, OrderStatusEnum.Canceled);

    private final String code;
    private final String desc;
    private final OrderStatusEnum sourceOrderStatus;
    private final OrderStatusEnum targetOrderStatus;

    OrderEventEnum(String code, String desc, OrderStatusEnum sourceOrderStatus, OrderStatusEnum targetOrderStatus) {
        this.code = code;
        this.desc = desc;
        this.sourceOrderStatus = sourceOrderStatus;
        this.targetOrderStatus = targetOrderStatus;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    public OrderStatusEnum getSourceOrderStatus() {
        return sourceOrderStatus;
    }

    public OrderStatusEnum getTargetOrderStatus() {
        return targetOrderStatus;
    }
}

3.3. 事件逻辑体

首先声明一个逻辑体基类,基类里面定义了事件过程数据缓存对象、事件条件校验方法、事件后处理方法、事件业务逻辑处理虚方法(需要由业务自己实现)。

@Slf4j
public abstract class BaseOrderFsmProcessor {
    private static final Map<Long, Object> FSM_DATA_MAP = new ConcurrentHashMap<>();

    /**
     * 执行业务逻辑
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     */
    public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
        log.info("OrderFSM_开始FSM事件:orderId={}", orderId);
        if (!checkRule(orderId, event)) {
            log.warn("OrderFSM_不满足条件,拒绝执行FSM事件:orderId={}", orderId);
            return true;
        }
        boolean b = process(orderId, event);
        log.info("OrderFSM_结束FSM事件:orderId={}", orderId);
        postHandler(orderId, event);
        return b;
    }

    /**
     * 业务逻辑实现类
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     * @throws Exception ex
     */
    public abstract boolean process(Long orderId, OrderEventEnum event) throws Exception;

    /**
     * 校验是否满足条件执行当亲process
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     */
    public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
        return true;
    }

    /**
     * 后置处理逻辑
     *
     * @param orderId 订单ID
     * @param event   事件类型
     */
    public void postHandler(Long orderId, OrderEventEnum event) throws Exception {

    }

    /**
     * 根据流程ID获取事件过程数据
     *
     * @param orderId 订单ID
     * @return 事件过程数据
     */
    public static Object getFsmData(Long orderId) {
        return FSM_DATA_MAP.remove(orderId);
    }

    /**
     * 根据流程ID设置事件过程数据
     *
     * @param orderId 订单ID
     * @param obj     事件过程数据
     */
    public static void setFsmData(Long orderId, Object obj) {
        FSM_DATA_MAP.put(orderId, obj);
    }

}

再跟进基类派生出不同事件对应的处理方法,一个事件要声明一个对应的方法:

@Slf4j
public class CreateOrderProcessor extends BaseOrderFsmProcessor {

    @Override
    public boolean process(Long orderId, OrderEventEnum event) throws Exception {
        log.info("业务逻辑执行中:className={},event={}", getClass().getSimpleName(), event.name());
        //TODO 模拟业务逻辑
        TimeUnit.MILLISECONDS.sleep(1000);
        setFsmData(orderId, Thread.currentThread().getName());

        log.info("业务逻辑执行完成");
        return true;
    }

    @Override
    public boolean checkRule(Long orderId, OrderEventEnum event) throws Exception {
        log.info("执行条件检查通过");
        return true;
    }

    @Override
    public void postHandler(Long orderId, OrderEventEnum event) throws Exception {
        log.info("执行后置处理逻辑");
        // TODO 将orderId的状态改为 event.getTargetOrderStatus()
    }
}

3.4. 事件注册工厂类

工厂类里封装了单例FSM执行的唯一入口和查询设置流程中间数据的入口。

@Slf4j
public class OrderFsmManager {

    private final Map<OrderEventEnum, BaseOrderFsmProcessor> orderProcessorMap = new HashMap<>();
    private volatile static OrderFsmManager orderFsmManager;

    private OrderFsmManager() {
        orderProcessorMap.put(OrderEventEnum.CreateOrder, new CreateOrderProcessor());
        orderProcessorMap.put(OrderEventEnum.Payment, new PaymentFsmProcessor());
    }

    /**
     * 获取fsm实例
     */
    public static OrderFsmManager getInstance() {
        if (orderFsmManager == null) {
            synchronized (OrderFsmManager.class) {
                if (orderFsmManager == null) {
                    orderFsmManager = new OrderFsmManager();
                }
            }
        }
        return orderFsmManager;
    }

    /**
     * 开始执行fsm事件
     *
     * @param orderId 订单ID
     * @param event   事件类型
     * @return 成功or失败
     */
    public boolean fireProcess(Long orderId, OrderEventEnum event) throws Exception {
        if (!orderProcessorMap.containsKey(event)) {
            throw new Exception(String.format("MediaProcessFSM没有匹配到事件:orderId=%s,currentOrderEvent=%s"
                    , orderId, event));
        }
        return orderProcessorMap.get(event).fireProcess(orderId, event);
    }

    /**
     * 根据流程ID获取事件过程数据
     *
     * @param orderId 订单ID
     * @return 事件过程数据
     */
    public Object getFsmData(Long orderId) {
        return BaseOrderFsmProcessor.getFsmData(orderId);
    }

    /**
     * 根据流程ID设置事件过程数据
     *
     * @param orderId 订单ID
     * @param obj     事件过程数据
     */
    public void setFsmData(Long orderId, Object obj) {
        BaseOrderFsmProcessor.setFsmData(orderId, obj);
    }
}

3.5. 简单做个测试

	public static void main(String[] args) throws Exception {
        OrderFsmManager orderFsmManager = OrderFsmManager.getInstance();
//        boolean b1 = orderFsmManager.fireProcess(1L, OrderEventEnum.CreateOrder);
//        boolean b2 = orderFsmManager.fireProcess(2L, OrderEventEnum.Payment);
//        System.out.println(String.format("orderId=%s,data=%s",1, orderFsmManager.getFsmData(1L)));
//        System.out.println(String.format("orderId=%s,data=%s",2, orderFsmManager.getFsmData(2L)));

        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                String threadName = "thread-" + finalI;
                Thread.currentThread().setName(threadName);

                try {
                    if (finalI%2==0){
                        boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.CreateOrder);
                    }else {
                        boolean b = orderFsmManager.fireProcess((long) finalI, OrderEventEnum.Payment);
                    }

                    System.out.println(String.format("threadName=%s,data=%s",threadName, orderFsmManager.getFsmData((long) finalI)));
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }).start();
        }


    }
11:17:39.213 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=2
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.221 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.213 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=1
11:17:39.213 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_开始FSM事件:orderId=0
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行条件检查通过
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行条件检查通过
11:17:39.223 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行中:className=CreateOrderProcessor,event=CreateOrder
11:17:39.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行中:className=PaymentFsmProcessor,event=Payment
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 业务逻辑执行完成
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=1
11:17:40.223 [thread-1] INFO org.yc.test.fsm4.processor.PaymentFsmProcessor - 执行后置处理逻辑
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=2
11:17:40.228 [thread-2] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-1,data=thread-1
threadName=thread-2,data=thread-2
11:17:40.246 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 业务逻辑执行完成
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.BaseOrderFsmProcessor - OrderFSM_结束FSM事件:orderId=0
11:17:40.247 [thread-0] INFO org.yc.test.fsm4.processor.CreateOrderProcessor - 执行后置处理逻辑
threadName=thread-0,data=thread-0

4. 结语

到这里FSM的封装就完成了,其中有一些设计可以根据自身技术选择进行调整,如工厂类的单例实现可以使用spring的bean注入方式,事件和状态的绑定关系可以外挂出来作为配置项等。

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接