状态模式-在工作中的实际应用

状态模式,一种不太常用的设计模式,但是在涉及状态流转的场景下非常好用,一般用来实现状态机。它的侧重点也在解耦,且具备良好的可扩展性。状态机也称有限状态机,一般包含三要素:事件(Event)、状态(State)、行为(Action)。其中,事件触发状态转移,状态转移后可能会做一些行为,也可能只转移状态,什么事情也不做,所以事件和状态是必须的,但是行为不是必须的。

1、背景:

公司主营港口无人驾驶,无人车需要与港口自动化设备交互,进行引导对位,从而进行自动化抓落箱。整个对位交互过程可以划分为如下几个状态:初始化->就绪->开始对位->对位中->单次对位完成->对位完成->锁车->解锁->对位结束。

2、状态模式使用:

定义一个状态机类AlignStateMachine,用来管理所有的事件与状态;定义一个抽象的事件AbstractAlignEvent,具体的事件继承该类,用来触发状态转移;定义一个抽象状态类AbstractAlignState,具体的状态继承该类,实现自己关心的方法;定义一个抽象的行为AbstractAlignAction,具体的行为继承该类,表示状态转移后要做的事情。以下,将结合Spring的事件实现对位状态机。

2.1、AlignStateMachine

状态机,其中包含所有的事件以及管理着每个无人车的状态。

java 复制代码
@Service
@Slf4j
public class AlignStateMachine {
    /**
     * key->无人车ID
     * value->无人车的状态
     */
    private final Map<String, AbstractAlignState> stateMachineMap = new ConcurrentHashMap<>();

    @Resource
    private AbstractAlignState alignInit;

    public AbstractAlignState getCurrentAlignState(String vehicleId) {
        return stateMachineMap.get(vehicleId);
    }

    public void goToTheNextAlignState(String vehicleId, AbstractAlignState alignState) {
        stateMachineMap.computeIfPresent(vehicleId, (k, v) -> {
            if (Objects.nonNull(stateMachineMap.get(vehicleId))) {
                return alignState;
            } else {
                return null;
            }
        });
    }

    public void removeAlignState(String vehicleId) {
        stateMachineMap.remove(vehicleId);
    }

    @EventListener
    public void initAlign(AlignInitEvent alignInitEvent) {
        log.info("无人车{}收到AlignInitEvent", alignInitEvent.getVehicleId());
        removeAlignState(alignInitEvent.getVehicleId());
        stateMachineMap.putIfAbsent(alignInitEvent.getVehicleId(), alignInit);
        stateMachineMap.get(alignInitEvent.getVehicleId()).initAlign(this, alignInitEvent);
        log.info("无人车{}处于初始化状态", alignInitEvent.getVehicleId());
    }

    @EventListener
    public void readyAlign(AlignReadyEvent alignReadyEvent) {
        log.info("无人车{}收到AlignReadyEvent", alignReadyEvent.getVehicleId());
        stateMachineMap.get(alignReadyEvent.getVehicleId()).readyAlign(this, alignReadyEvent);
    }
    // 省略其它事件...
}

2.2、AbstractAlignEvent

继承Spring的Event,方便使用Spring的Event进行管理。此外,将一些公共字段提取到此类中,例如无人车车号

java 复制代码
@Getter
public abstract class AbstractAlignEvent extends ApplicationEvent {
    /**
     * 车号
     */
    private final String vehicleId;

    public AbstractAlignEvent(Object source, String vehicleId) {
        super(source);
        this.vehicleId = vehicleId;
    }

}

AlignInitEvent->初始化事件;

java 复制代码
public class AlignInitEvent extends AbstractAlignEvent {
    public AlignInitEvent(Object source, String vehicleId) {
        super(source, vehicleId);
    }
}

省略其它事件...

2.3、AbstractAlignState

包含所有事件以及默认实现,默认实现只打印日志,具体的实现由对应的子类实现感兴趣的事件。有了默认实现,方便进行调试,例如发布了某个事件,但是却没有达到预期的效果,通过日志可以看到一般是状态没有达到预期的状态。

java 复制代码
@Service
@Slf4j
public abstract class AbstractAlignState {

    @Resource
    private ApplicationContext applicationContext;

    public <T extends AbstractAlignState> T getAlignStateInstance(Class<T> beanType) {
        return applicationContext.getBean(beanType);
    }

    protected void initAlign(AlignStateMachine alignStateMachine, AlignInitEvent alignInitEvent) {
        log.info("无人车{}当前处于{}状态, 忽略{}事件",
                alignInitEvent.getVehicleId(),
                alignStateMachine.getCurrentAlignState(alignInitEvent.getVehicleId()).getClass().getSimpleName(),
                alignInitEvent.getClass().getSimpleName()
        );
    }

    protected void readyAlign(AlignStateMachine alignStateMachine, AlignReadyEvent alignReadyEvent) {
        log.info("无人车{}当前处于{}状态, 忽略{}事件",
                alignReadyEvent.getVehicleId(),
                alignStateMachine.getCurrentAlignState(alignReadyEvent.getVehicleId()).getClass().getSimpleName(),
                alignReadyEvent.getClass().getSimpleName()
        );
    }
    // 省略其它事件...
}

具体的状态类子类实现:

java 复制代码
@Service
@Slf4j
public class AlignInit extends AbstractAlignState {

    @Resource
    private ReportIntoCraneAction reportIntoCraneAction;
    @Resource
    private SetVehicleArriveAction setVehicleArriveAction;
    @Resource
    private AlignResetAction alignResetAction;

    @Override
    protected void initAlign(AlignStateMachine alignStateMachine, AlignInitEvent alignInitEvent) {
        log.info("无人车{}收到AlignInitEvent", alignInitEvent.getVehicleId());
        alignResetAction.act(alignStateMachine.getCurrentAlignState(alignInitEvent.getVehicleId()), alignInitEvent);
        reportIntoCraneAction.act((AlignInit) alignStateMachine.getCurrentAlignState(alignInitEvent.getVehicleId()), alignInitEvent);
    }

    @Override
    protected void readyAlign(AlignStateMachine alignStateMachine, AlignReadyEvent alignReadyEvent) {
        AlignReady alignReady = getAlignStateInstance(AlignReady.class);
        alignStateMachine.goToTheNextAlignState(alignReadyEvent.getVehicleId(), alignReady);
        setVehicleArriveAction.act(alignReady, alignReadyEvent);
        log.info("无人车{}处于就绪状态", alignReadyEvent.getVehicleId());
    }
}

其中,AlignInit状态只关心initAlign和readyAlign事件,其它事件不关心,走默认实现,只打印日志。监听到对应事件后,将状态转移至下一状态,不过initAlign事件比较特殊,因为遇到该事件时先进入初始化状态,然后执行对应的行为,在状态机中提前初始化好了。其它都是在事件中发生状态转移,然后执行对应的行为。后面的状态如AlignReady、AlignBegin、Aligning、SingleAlignFinish、AlignFinish、AlignLocked、 AlignUnlocked、AlignEnd,只实现自己关心的事件,做自己该做的行为。

2.4、AbstractAlignAction

定义一个行为方法,具体的行为由子类去实现。

java 复制代码
public abstract class AbstractAlignAction<T extends AbstractAlignState, S extends AbstractAlignEvent> {
    public abstract void act(T alignState, S alignEvent);
}

具体的行为实现:

java 复制代码
@Service
public class ReportIntoCraneAction extends AbstractAlignAction<AlignInit, AlignInitEvent> {
    @Override
    public void act(AlignInit alignState, AlignInitEvent alignInitEvent) {
        // 省略具体实现
    }
}

省略其它行为实现...

2.5、发布对应事件,触发状态机流转:

java 复制代码
@Service
public class PncCommandService {
    
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    private void handleXXX(String vehicleId) {
        applicationEventPublisher.publishEvent(new AlignInitEvent(this, vehicleId));
    }
    // 省略其它事件发布
}

3、总结:

当发现程序在设计上符合状态机时,采用状态模式可以使程序更加解耦,每个状态转移要做什么事情都很清晰,未来新增状态也很利于扩展。此外,合理的代码包结构例如状态都放在state包下,事件都放在event包下,行为都放在action包下,代码层次一目了然,利于维护。结合Spring的事件,在Java中如此实现状态机更加优雅一些。

相关推荐
触底反弹4 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
杨充1 天前
10.可测试性实战设计
设计模式·开源·代码规范
杨充1 天前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 天前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 天前
2.面向对象的特性
设计模式
杨充1 天前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 天前
8.反模式与坏味道
设计模式·开源·代码规范
杨充1 天前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法