有限状态机的4中实现对比

本文对比分析了有限状态机的四种实现方法:基于Switch、State模式、状态集合和枚举。通过一个地铁闸口状态变化的例子,探讨了各种实现的优缺点。State模式简化了逻辑但增加了代码复杂度,状态集合更直观且易于扩展,枚举实现直观但可能引发滥用问题。
摘要由CSDN通过智能技术生成

有限状态机的4种实现对比

在日常工作过程中,我们经常会遇到状态的变化场景,例如订单状态发生变化,商品状态的变化。这些状态的变化,我们称为有限状态机,缩写为FSM( F State Machine).。之所以称其为有限,是因为这些场景中的状态往往是可以枚举出来的有限个的,所以称其为有限状态机。下面我们来看一个具体的场景例子。

简单场景:

地铁进站闸口的状态有两个:已经关闭、已经开启两个状态。刷卡后闸口从已关闭变为已开启,人通过后闸口状态从已开启变为已关闭。

01 遇到这类问题,在编码时我们应该如何处理呢?

  • 基于Switch
  • 基于状态集合
  • 基于State模式
  • 基于枚举的实现

下面我们针对每一种实现方式进行分析。场景分解后会有一下2种状态4种情况出现:

Index State Event NextState Action
1 闸机口 LOCKED 投币 闸机口 UN_LOCKED 闸机口打开闸门
2 闸机口 LOCKED 通过 闸机口 LOCKED 闸机口警告
3 闸机口 UN_LOCKED 投币 闸机口 UN _LOCKED 闸机口退币
4 闸机口 UN_LOCKED 通过 闸机口 LOCKED 闸机口关闭闸门

针对以上4种请求,共拆分了5个Test Case

T01

Given:一个Locked的进站闸口

When: 投入硬币

Then:打开闸口

T02

Given:一个Locked的进站闸口

When: 通过闸口

Then:警告提示

T03

Given:一个Unocked的进站闸口

When: 通过闸口

Then:闸口关闭

T04

Given:一个Unlocked的进站闸口

When: 投入硬币

Then:退还硬币

T05

Given:一个闸机口

When: 非法操作

Then:操作失败

代码地址:https://gitlab.com/tengbai/fsm-java

项目中共有4中状态机的实现方式。

  • 基于Switch语句实现的有限状态机,代码在master分支

  • 基于State模式实现的有限状态机。代码在state-pattern分支

  • 基于状态集合实现的有限状态机。代码在collection-state分支

  • 基于枚举实现的状态机。代码在enum-state分支

01.01 使用Switch来实现有限状态机

这种方式只需要懂得Java语法及可以实现出来。先看代码,然后我们在讨论这种实现方式是否好。

EntranceMachineTest.java

package com.page.java.fsm;

import com.page.java.fsm.exception.InvalidActionException;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.BDDAssertions.then;

class EntranceMachineTest {
   

    @Test
    void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() {
   
        EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED);

        String result = entranceMachine.execute(Action.INSERT_COIN);

        then(result).isEqualTo("opened");
        then(entranceMachine.getState()).isEqualTo(EntranceMachineState.UNLOCKED);
    }

    @Test
    void should_be_locked_and_alarm_when_pass_given_a_entrance_machine_with_locked_state() {
   
        EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED);

        String result = entranceMachine.execute(Action.PASS);

        then(result).isEqualTo("alarm");
        then(entranceMachine.getState()).isEqualTo(EntranceMachineState.LOCKED);
    }

    @Test
    void should_fail_when_execute_invalid_action_given_a_entrance_with_locked_state() {
   
        EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED);

        assertThatThrownBy(() -> entranceMachine.execute(null))
                .isInstanceOf(InvalidActionException.class);
    }

    @Test
    void should_locked_when_pass_given_a_entrance_machine_with_unlocked_state() {
   
        EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.UNLOCKED);

        String result = entranceMachine.execute(Action.PASS);

        then(result).isEqualTo("closed");
        then(entranceMachine.getState()).isEqualTo(EntranceMachineState.LOCKED);
    }

    @Test
    void should_refund_and_unlocked_when_insert_coin_given_a_entrance_machine_with_unlocked_state() {
   
        EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.UNLOCKED);

        String result = entranceMachine.execute(Action.INSERT_COIN);

        then(result).isEqualTo("refund");
        then(entranceMachine.getState()).isEqualTo(EntranceMachineState.UNLOCKED);
    }
}

Action.java

public enum Action {
   
    INSERT_COIN,
    PASS
}

EntranceMachineState.java

public enum EntranceMachineState {
   
    UNLOCKED,
    LOCKED
}

InvalidActionException.java

package com.page.java.fsm.exception;

public class InvalidActionException extends RuntimeException {
   
}

EntranceMachine.java

package com.page.java.fsm;

import com.page.java.fsm.exception.InvalidActionException;
import lombok.Data;

import java.util.Objects;

@Data
public class EntranceMachine {
   

    private EntranceMachineState state;

    public EntranceMachine(EntranceMachineState state) {
   
        this.state = state;
    }

    public String execute(Action action) {
   
        if (Objects.isNull(action)) {
   
            throw new InvalidActionException();
        }

        if (EntranceMachineState.LOCKED.equals(state)) {
   
            switch (action) {
   
                case INSERT_COIN:
                    setState(EntranceMachineState.UNLOCKED);
                    return open();
                case PASS:
                    return alarm();
            }
        }

        if (EntranceMachineState.UNLOCKED.equals(state)) {
   
            switch (action) {
   
                case PASS:
                    setState(EntranceMachineState.LOCKED);
                    return close();
                case INSERT_COIN:
                    return refund();
            }
        }
        return null;
    }

    private String refund() {
   
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值