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

状态模式,一种不太常用的设计模式,但是在涉及状态流转的场景下非常好用,一般用来实现状态机。它的侧重点也在解耦,且具备良好的可扩展性。状态机也称有限状态机,一般包含三要素:事件(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中如此实现状态机更加优雅一些。

相关推荐
刷帅耍帅38 分钟前
设计模式-享元模式
设计模式·享元模式
刷帅耍帅41 分钟前
设计模式-模版方法模式
设计模式
刷帅耍帅2 小时前
设计模式-桥接模式
设计模式·桥接模式
MinBadGuy3 小时前
【GeekBand】C++设计模式笔记5_Observer_观察者模式
c++·设计模式
刷帅耍帅4 小时前
设计模式-生成器模式/建造者模式Builder
设计模式·建造者模式
蜡笔小新..1 天前
【设计模式】软件设计原则——开闭原则&里氏替换&单一职责
java·设计模式·开闭原则·单一职责原则
性感博主在线瞎搞1 天前
【面向对象】设计模式概念和分类
设计模式·面向对象·中级软件设计师·设计方法
lucifer3111 天前
JavaScript 中的组合模式(十)
javascript·设计模式
lucifer3111 天前
JavaScript 中的装饰器模式(十一)
javascript·设计模式
蜡笔小新..1 天前
【设计模式】软件设计原则——依赖倒置&合成复用
设计模式·依赖倒置原则·合成复用原则