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

2023-11-09

有限状态机的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() {
   
        return 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

有限状态机的4中实现对比 的相关文章

  • Android:如何暂停和恢复可运行线程?

    我正在使用 postDelayed 可运行线程 当我按下按钮时 我需要暂停并恢复该线程 请任何人帮助我 这是我的主题 protected void animation music6 music4 postDelayed new Runnab
  • “源兼容性”和“目标兼容性”有什么区别?

    之间有什么关系 区别sourceCompatibility and targetCompatibility 当它们设置为不同的值时会发生什么 根据工具链和兼容性 https docs gradle org current userguide
  • 在不支持 CAS 操作的处理器上进行 CompareAndSet

    今天 我在一次采访中被问到下一个问题 如果您在具有不支持 CAS 操作的处理器的机器上调用 AtomicLong 的compareAndSet 方法 会发生什么情况 您能否帮我解决这个问题 并在可能的情况下提供一些全面描述的链接 From
  • 如何在 Android 应用程序中隐藏 Flutterwave API 密钥

    我正在构建一个 Android 应用程序 目前正在将 Flutterwave 集成到我的应用程序中以进行支付 建议我永远不要将 Flutterwave API 密钥放在我的应用程序上 那么我该如何隐藏这些键呢 我正在使用 Retrofit
  • Java LostFocus 和 InputVerifier,按反向制表符顺序移动

    我有一个 GUI 应用程序 它使用 InputVerifier 在产生焦点之前检查文本字段的内容 这都是很正常的 然而 昨天发现了一个问题 这似乎是一个错误 但我在任何地方都找不到任何提及它的地方 在我将其报告为错误之前 我想我应该问 我在
  • 通过Zuul上传大文件

    我在通过 zuul 上传大文件时遇到问题 我正在使用 apache commons 文件上传 https commons apache org proper commons fileupload https commons apache o
  • 有人用过 ServiceLoader 和 Guice 一起使用吗?

    我一直想通过我们的应用程序 构建系统进行更大规模的尝试 但更高的优先级不断将其推到次要地位 这似乎是加载 Guice 模块的好方法 并且避免了关于 硬编码配置 的常见抱怨 单个配置属性很少会自行更改 但您几乎总是会有一组配置文件 通常用于不
  • 自定义列表字段点击事件

    我正在编写一个应用程序 其中我创建了用于显示列表视图的自定义列表字段 我的 CustomListField 包含连续的一个图像和文本 我正在通过单击列表字段行获取字段更改侦听器 但我也想将字段更改侦听器放在图像上 谁能告诉我我该怎么做 这是
  • 如何使用 Java 引用释放 Java Unsafe 内存?

    Java Unsafe 类允许您按如下方式为对象分配内存 但是使用此方法在完成后如何释放分配的内存 因为它不提供内存地址 Field f Unsafe class getDeclaredField theUnsafe Internal re
  • 为什么在将 String 与 null 进行比较时会出现 NullPointerException?

    我的代码在以下行中出现空指针异常 if stringVariable equals null 在此语句之前 我声明了 stringVariable 并将其设置为数据库字段 在这个声明中 我试图检测该字段是否有null值 但不幸的是它坏了 有
  • 如何使用双重调度来分析图形基元的交集?

    我正在分析图形基元 矩形 直线 圆形等 的交互并计算重叠 相对方向 合并等 这被引用为双重调度的一个主要示例 例如维基百科 http en wikipedia org wiki Double dispatch 自适应碰撞算法通常要求 不同的
  • 如何在 IntelliJ IDEA 中运行 akka actor

    来自 Akka 网站文档 然后 这个主要方法将创建所需的基础设施 运行演员 启动给定的主要演员并安排 一旦主要参与者终止 整个应用程序就会关闭 因此 您将能够使用类似于以下的命令运行上面的代码 下列的 java classpath akka
  • Android - 存储对ApplicationContext的引用

    我有一个静态 Preferences 类 其中包含一些应用程序首选项和类似的内容 可以在那里存储对 ApplicationContext 的引用吗 我需要该引用 以便我可以在不继承 Activity 的类中获取缓存文件夹和类似内容 你使用的
  • Java 8 Stream,获取头部和尾部

    Java 8 引入了Stream http download java net jdk8 docs api java util stream Stream html类似于 Scala 的类Stream http www scala lang
  • Tomcat 6 未从 WEB-INF/lib 加载 jar

    我正在尝试找出我的 tomcat 环境中的配置问题 我们的生产服务器正在运行 tomcat 安装并从共享 NFS 挂载读取战争 然而 当我尝试使用独立的盒子 及其配置 进行同样的战争时 我收到下面发布的错误 有趣的是 如果我将 WEB IN
  • Java 中清除嵌套 Map 的好方法

    public class MyCache AbstractMap
  • Selenium 单击在 Internet Explorer 11 上不起作用

    我尝试在 Internet Explorer 上单击 selenium 但它不起作用 我努力了element click moveToElement element click build perform javascript没事了 事实上
  • 检测到 JVM 正在关闭

    我有一个使用 addShutdownHook 处理 Ctrl C 的 Swing 应用程序 它工作正常 直到我的关闭任务之一调用一个在正常情况下更改 JLabel 文本的函数 此时它挂起 我认为问题是 Swing EDT 已终止或正在等待某
  • 如何让 Firebase 与 Java 后端配合使用

    首先 如果这个问题过于抽象或不适合本网站 我想表示歉意 我真的不知道还能去哪里问 目前我已经在 iOS 和 Android 上开发了应用程序 他们将所有状态保存在 Firebase 中 因此所有内容都会立即保存到 Firebase 实时数据
  • 设置 TreeSet 的大小

    有没有办法像数组一样对 Java 集合中的 TreeSet 进行大小限制 例如我们在数组中 anArray new int 10 数组具有固定长度 在创建数组时必须指定该长度 A TreeSet当您向其中添加元素时会自动增长 您无法设置其大

随机推荐

  • 数据分析入门宝藏!《Python数据分析-从入门到实践》

    在大数据 人工智能时代 数据无处不在 无论处于哪种行业 能够掌握一定的数据分析技能必然是职场的加分项 本笔记提供了丰富的学习内容 包含230个快速示例 17个案例 4个项目 力求为读者打造一本 学习入门 应用 实践一体化 的的Python数
  • Presto 常用配置及操作

    一 介绍 Presto是一个开源的分布式SQL查询引擎 适用于交互式分析查询 数据量支持GB到PB字节 Presto的设计和编写完全是为了解决像Facebook这样规模的商业数据仓库的交互式分析和处理速度的问题 推荐阅读 Presto实现原
  • DVWA 通关XSS(Stored)

    存储型XSS 持久化跨站脚本 持久性体现在XSS代码不是在某个参数 变量 中 而是写进数据库文件等可以永久保存数据的介质中 存储型XSS通常发生在留言板等地方 可以在留言板位置进行留言 将恶意代码写进数据库中 Low 没有任何过滤 直接使用
  • 开源云同步的markdown写作软件——Yosoro

    文章目录 前言 简便的项目管理 舒服的写作体验 支持one driver 存在缺点 前言 Yosoro是一款支持在Win Linux macOS上使用的写作软件 它的界面设计以及交互上表达出的极简主义可以让用户们可以完全沉浸于自己写作世界
  • MyBatis学习——第四篇(拦截器和拦截器分页实现)

    MyBatis架构体图 1 mybatis核心对象 从MyBatis代码实现的角度来看 MyBatis的主要的核心部件有以下几个 SqlSession 作为MyBatis工作的主要顶层API 表示和数据库交互的会话 完成必要数据库增删改查功
  • 【git体验】git基础-3目录之间关系

    1 git目录和工作目录 Git目录并不是Bare repo 而是本地的代码库 即用git init命令在根目录创建的 git 目录 类似SVN的 svn 目录 这个目录就是git实现分布式代码管理的关鍵了 工作目录就是 git的上級目录
  • Angular&TypeScript 经验技巧

    TypeScript 变量声明 var 变量名 类型 值 基本类型 数据类型 关键字 描述 任意类型 any 声明为 any 的变量可以赋予任意类型的值 数字类型 number 双精度 64 位浮点值 它可以用来表示整数和分数 let bi
  • 使用HAL库开发STM32:使用Timer输出PWM信号

    文章目录 目的 基础说明 输出PWM信号 总结 目的 单片机输出PWM信号是很常用的一种功能需求 STM32中通常使用Timer来输出PWM信号 这篇文章将对相关内容做个说明 基础说明 在使用Timer输出PWM信号需要了解一些Timer的
  • Spring Boot, 访问入口配置

    HTTP Server port server port 8080 Make the application accessible on the given context path http localhost 8080 myapp se
  • openGL结合光照与纹理

    openGL系列文章目录 文章目录 openGL系列文章目录 前言 一 实现思路 二 代码 1 c 主程序 2 顶点着色器 3 片元着色器 运行效果 参考 源码下载 前言 在光照模型中 都是假设我们使用按ADS 定义的光源 照亮按ADS 定
  • Python计算商品复购率

    1 Python计算产品复购率 需求 给出数据商品购买数据 数据格式 csv 包含 购买月份 手机号 根据该数据计算产品的复购率 复购率算法 算法一 单位时间内 按每月 R 复购人数 总购买人数 算法二 单位时间内 按每月 R 复购交易次数
  • 应用usb_cam同时打开多个摄像头方法

    最近由于项目需要 需要同时开启多个摄像头 虽然可以用opencv去写对应的摄像头开启的程序 但是 还是想用ros中提供的usb cam去打开多个摄像头 通过usb cam去打开一个摄像头 不用下载源码 可以直接安装usb cam去调用lau
  • 使用GDI/GDI+绘制到D3D9缓冲区的方法

    这个其实是3D绘图里嵌入2D绘图的传统方式 D3D9直接使用GDI GDI 就可以画图 只不过需要额外的设置 而且只支持RGB和XRGB 不支持ARGB 因此这种方法比较适合合成UI元素和不透明的纹理贴图 不适合将要进行AlphaBlend
  • #Mybatis 关于mybatis的一级缓存

    本篇文章主要是为了帮助自己总结和加深理解 若能帮助到其他小伙伴也是极好的 基本介绍 Mybatis中支持一级缓存和二级缓存 一级缓存是默认开启的并且不能关闭 二级缓存默认关闭 可根据需要进行手动开启 总体来说Mybatis的一二级缓存的最终
  • Shell 排序法 - 改良的插入排序

    说明 插入排序法由未排序的后半部前端取出一个值 插入已排序前半部的适当位置 概念简单但速度不快 排序要加快的基本原则之一 是让后一次的排序进行时 尽量利用前一次排序后的结果 以加快排序的速度 Shell排序法即是基于此一概念来改良插入排序法
  • css设置div上下左右均居中 、底部居中

    css设置div或盒子居中 垂直居中 左右居中 底部居中 类型一 固定宽度高度 html代码 div class login container div class login box div 内容 div div div 2 css 外部
  • 软件测试入坑建议

    本科非计算机专业 在深圳做了四年软件测试工作 从之前的一脸懵的点点点 到现在会点自动化测试 说一点点非计算机专业人员从事软件测试的心得体会 仅供参考交流 如果你是非计算机专业 毕业不久 软件测试工作门槛相对较低 比较容易入门 建议入职互联网
  • Halcon学习---玻璃瓶口的缺陷检测

    inspect bottle mouth hdev 巧妙运用了极坐标变换法 细节很精细 值得学习 tuning parameters SmoothX 501 ThresholdOffset 25 MinDefectSize 50 initi
  • 华为机试-----集五福作为近年来大家喜闻乐见迎新春活动,集合爱国福、富强福、和谐福、友善福、敬业福即可分享超大红包

    题目 以0和1组成的长度为5的字符串代表每个人所得到的福卡 每一位代表一种福卡 1表示已经获得该福卡 单类型福卡不超过1张 随机抽取一个小于10人团队 求该团队最多可以集齐多少套五福 输入描述 输入若干个 11010 00110 的由0 1
  • 有限状态机的4中实现对比

    有限状态机的4种实现对比 在日常工作过程中 我们经常会遇到状态的变化场景 例如订单状态发生变化 商品状态的变化 这些状态的变化 我们称为有限状态机 缩写为FSM F State Machine 之所以称其为有限 是因为这些场景中的状态往往是