有限状态机的定义及相关示例

简介

文章的主要内容是有限状态机的定义,在编程中将有限状态机做适当调整,适应真实的业务场景,最后提供完整的代码示例。

什么是有限状态机

这里关于有限状态机的介绍是出自通义千问的回答

定义

有限状态机(Finite State Machine, FSM)是一种数学模型,用于描述一个系统在不同状态下对外部事件的响应方式。它由一组有限的状态、一个初始状态、一个或多个终态(可选)以及一组状态之间的转移规则构成。

核心概念

  • 初始状态(Initial State):系统开始时所处的状态。
  • 状态(States):系统可能处于的任何一种情况。
  • 终态(Final States):系统可以达到的结束状态,也可能不存在。
  • 输入(Input):外部事件或信号,触发状态间的转移。
  • 输出(Output):与每个状态关联的动作或结果。
  • 状态转移(State Transitions):根据输入从一个状态转移到另一个状态的规则。

使用场景

  • 在计算机科学中,用于协议解析、编译器设计、游戏AI、硬件电路设计等领域。
  • 在商业逻辑中,如订单流程管理、用户权限控制等场景,有限状态机帮助清晰地定义和管理复杂的多步骤过程。

在编程中对有限状态机的改造

以用有限状态机实现商品状态的管理为例

上面的定义划分的很细、很清晰,有时在实际应用中是不需要完全依照一个理论实施的,毕竟要实事求是,不能生搬硬套。

简化核心组件

我们可以把输出和状态转移合并成一个概念:动作。动作是状态遇到事件应执行的相应操作。

把初始状态和状态合并成“现态”,也就是当前的状态。

根据有限状态机的定义,它适用于系统从一个初始状态,不断接收事件并不断改变状态的场景。在编程中,通常都是将实体从一个状态转变成另一个状态,很少涉及一次转变多个状态的情况。所以这里把核心概念进行简化适应真实的业务场景。

经过简化,有限状态机的核心概念应该如下:

  • 现态:表示实体当前的状态
  • 终态:实体接收事件后,在执行了相应动作转变到另一个状态
  • 事件:触发现态到终态转变的条件(将输入改名为事件)
  • 动作:现态接收事件应执行的操作

示例

1.定义商品状态

public enum ProductStatusEnum {
    WAITING_AUDIT,
    FAILED_OF_AUDIT,
    SUCCESS_OF_AUDIT,
    ON_SHELVES,
    OFF_SHELVES,
    SOLD_OUT,
    ;
}

2.实现业务中的有限状态机

2.1为有限状态机实现必要的抽象

有限状态机入口的抽象

为使用有限状态机提供一个入口。由于有限状态机的组件会比较多,如果不提供一个统一的使用入口,对不了解有限状态机原理的人来说不太友好。


/**
 * 商品状态机的执行器
 * 接收现态、事件和终态,进行状态转换
 * @author manem
 */
@Component
@Data
public class FSMProductExecutor {
    @Resource
    ApplicationContext ctx;
    private AbstractStatusHandler current;
    private IEvent event;

    private FSMProductExecutor() {
    }

    public void init(ProductStatusEnum current, ProductStatusEnum target) {
        // 设置现态对应的处理器
        setCurrent(matchHandler(current));
        // 设置转变到终态应触发的事件
        setEvent(matchEvent(target));
    }

    public IAction transitionStatus() {
        // 提取现态可接受的事件
        for (AbstractStatusHandler.EventMatcher matcher : current.getMatchers()) {
            // 判断事件是否匹配
            if (matcher.getEvent().equals(this.event)) {
                // 调用匹配事件对应的操作
                return matcher.getAction();
            }
        }
        throw MightException.ins(
                CodeEnum.INTERNAL_ERR.getCode(),
                String.format("当前状态<%s>不支持处理该事件<%s>", this.current.getStatus(), this.event)
        );
    }

    private AbstractStatusHandler matchHandler(ProductStatusEnum status) {
        AbstractStatusHandler fsmStatus;
        switch (status) {
            case SUCCESS_OF_AUDIT:
                fsmStatus = ctx.getBean(SuccessAuditHandler.class);
                break;
                // 这里定义不同状态的处理器
                ...
            default:
                throw new MightException(CodeEnum.INTERNAL_ERR);
        }
        return fsmStatus;
    }

    private EventEnum matchEvent(ProductStatusEnum status) {
        EventEnum fsmEvent;
        switch (status) {
            case SUCCESS_OF_AUDIT:
                fsmEvent = EventEnum.AUDIT_PASS;
                break;
                // 这里定义不同状态对应的事件枚举
                ...
            default:
                throw new MightException(CodeEnum.INTERNAL_ERR);
        }
        return fsmEvent;
    }
}

动作的抽象

不同事件会触发不同的动作,通过将动作这一行为进行抽象,有利于将业务逻辑解耦。后续增加事件,只需要添加不同的执行动作即可,不需要变动相关的业务代码。

/**
 * 将状态机的动作抽象出来,以应对不同业务的状态实现状态机
 * @author manem
 */
public interface IAction<P, R> {
    /**
     * 状态接收对应事件后可执行的操作
     * @param params
     * @return
     */
    R run(P params);
}

事件的抽象

为了实现有限状态机执行器的复用,这里为事件提供一个统一的接口。

public interface IEvent {
}

现态处理器的抽象

定义现态可接收的事件以及相应的动作

/**
 * 定义现态可接受的事件和相应的执行动作
 * @author manem
 */
public abstract class AbstractStatusHandler {
    protected ProductStatusEnum status;
    /**
     * 现态在这里定义可接受的事件和动作,触发相应事件可以转变到对应的终态
     * 未定义的事件则表示不支持转变到的终态
     */
    protected List<EventMatcher> matchers;

    public List<EventMatcher> getMatchers() {
        return matchers;
    }

    public ProductStatusEnum getStatus() {
        return status;
    }

    /**
     * 定义现态接受的事件和执行的动作
     */
    @Data
    public static final class EventMatcher {
        private IEvent event;
        /**
         * 接受事件后允许变更到的下一个状态
         */
        private IAction action;

        public EventMatcher(IEvent event, IAction action) {
            this.event = event;
            this.action = action;
        }
    }
}
2.2定义事件
public enum EventEnum implements IEvent {
    /**
     * 审核相关的事件
     */
    AUDIT_PASS, AUDIT_FAIL,
    /**
     * 上下架相关的事件
     */
    PUSH_SHELVE, PULL_SHELVE,
    /**
     * 售罄
     */
    STORE_EMPTY,
    /**
     * 编辑
     */
    EDIT
}
2.3创建使用有限状态机的入口

参考上面的章节"有限状态机入口的抽象"

2.4实现不同状态在不同事件下的切换

商品的状态比较多,这里以其中一个“待审核”状态为例,其他状态可以以此类推实现

定义待审核的状态处理器 

import cn.hutool.core.collection.CollUtil;
import javax.annotation.Resource;


@Component
public class WaitingAuditHandler extends AbstractStatusHandler {
    // 由于IAction的实现类比较多,这里使用beanName的方式指定应绑定哪个bean
    // 默认情况,bean的name是类名首字母小写
    @Resource(name = "auditSuccessAction")
    IAction auditSuccess;
    @Resource(name = "auditFailAction")
    IAction auditFail;
    @Resource(name = "reAuditAction")
    IAction reEdit;
    // 定义现态
    protected ProductStatusEnum status = ProductStatusEnum.WAITING_AUDIT;

    @Override
    public List<EventMatcher> getMatchers() {
        if (CollUtil.isEmpty(this.matchers)) {
            this.matchers = new ArrayList<>();
            // 定义待审核状态下可接收的事件和对应的动作
            this.matchers.add(new EventMatcher(EventEnum.AUDIT_PASS, auditSuccess));
            this.matchers.add(new EventMatcher(EventEnum.AUDIT_FAIL, auditFail));
            this.matchers.add(new EventMatcher(EventEnum.EDIT, reEdit));
        }
        return matchers;
    }
}

定义待审核状态相关的动作实现

下面是审核通过动作的实现,其他两个动作的实现可以参考它完善

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.might.components.fsm.IAction;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;

// 审核通过应该执行的操作
@Component
public class AuditSuccessAction implements IAction<Long, Boolean> {
    @Resource
    IProductService productService;
    @Resource
    IProductSkuService productSkuService;
    @Override
    public Boolean run(Long params) {
        // 审核通过应执行的具体业务逻辑
        LambdaUpdateWrapper<ProductPO> productUpdateWrapper = Wrappers.lambdaUpdate(ProductPO.class).eq(ProductPO::getId, params)
                .set(ProductPO::getStatus, ProductStatusEnum.SUCCESS_OF_AUDIT);
        if (productService.update(productUpdateWrapper)) {
            LambdaUpdateWrapper<ProductSkuPO> skuUpdateWrapper = Wrappers.lambdaUpdate(ProductSkuPO.class).eq(ProductSkuPO::getPid, params)
                    .eq(ProductSkuPO::getStatus, ProductStatusEnum.WAITING_AUDIT)
                    .set(ProductSkuPO::getStatus, ProductStatusEnum.SUCCESS_OF_AUDIT);
            return productSkuService.update(skuUpdateWrapper);
        }
        return false;
    }
}

完整目录结构

补充

由此可见一个完整的有限状态机会有很多组件,整体实现起来也较为复杂。所以当业务的状态数比较少时,就不要用有限状态机了,只会徒增系统的复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值