【设计模式】行为型设计模式之 状态模式,带你探究有限状态机FSM的三种实现方式

什么是有限状态机

Finite state Machine FSM 简称状态机:状态机由三部分组成,状态(State) 事件(Event) 和动作(Action)组成。
其中事件也被称为转移条件,事件触发状态的转移和动作的执行。不过动作不是必须的,也可能只存在状态转移。
状态模式一般用来实现状态机,不过状态机除了使用状态模式实现还可以使用分支判断法和查表法。

状态机案例

实现马里奥状态转换的状态机,可以使用三种方法实现。

  1. 分支判断法 使用 if-else 硬编码判断
  2. 使用查表法判断 根据数据库的二维表配置对应状态和事件来确定执行的动作
  3. 使用状态模式

分支判断法实现状态机

对于简单地状态机,分支判断法也是可以接受的,但是对于复杂的状态机,容易漏写某个状态转移,并且大量的 if-esle 充斥代码是的代码的可读性和可维护性都很差,容易产生 bug。

//定义状态枚举
public enum State{
    SMALL(0),
    SUPER(1),
    FIRE(2),
    CAPE(3);
    private int value;

    private State (int value){
        this.value=value;
    }
    public int getValue(){
        return value;        
    }
}
//实现状态机
public class MarioStateMachine{
    private int score;
    private State currentState;
    
    //构造函数,定义初始分数和状态
    public MarioStateMachine(){
        this.score=0;
        this.currentState = State.SMALL;
    }

    //定义四个事件方法,方法中直接包含事件触发后的动作逻辑
    
    //事件E1 吃到蘑菇
    public void obtainMushRoom(){
        
    }
    //事件E2 获得斗篷
    public void obtainCape(){
        
    }
    //事件E3 获得火焰
    public void obtainFireFlower(){
        
    }
    //事件E4 遇到怪物 这里举例,只填充该方法内容
    public void ontainMonster(){
        if(currentState=State.SMALL){
            this.currentState=State.SMALL;
            this.score-=100;
        }
        if(currentState=State.CAPE){
            this.currentState=State.SMALL;
            this.score-=200;
        }
        if(currentState=State.FIRE){
            this.currentState=State.SMALL;
            this.score-=300;
        }
    }
    
    public int getScore(){
        return score;
    }
    public State getCurrentState(){
        return state;
    }
}

查表法实现状态机

分支判断法可以处理简单地状态机,对于复杂的状态机可以使用查表法。并且状态机的状态转移图可以表示状态转移的动作和事件外也可以用状态转移表表示。

E1 吃到蘑菇E2 获得斗篷E3 获得火焰E4 遇到怪物
SmallSuper +100Cape +200Fire +300——
super——Cape +200Fire +300SMALL -100
Cape——————SMALL -200
Fire——————SMALL -300

对于上方简单的案例,使用二维数组就可以表示状态的转移和对应的动作产生的分数加减了。具体代码如下

//定义事件枚举
public enum Event{
    OBTAIN_MUSHROOM(0),
    OBTAIN_CAPE(1),
    OBTAIN_FIRE(2),
    MEET_MONSTER(3);
    //其他省略
}
//实现状态机
public class MarioStateMachine{
    private int score;
    private State currentState;

    //定义二维数组,保存状态转移信息
    private static final State[][] transitionTable = {
        {SUPER,CAPE,FIRE,SMALL},
        {SUPER,CAPE,FIRE,SMALL},
        {CAPE,CAPE,CAPE,SMALL},
        {FIRE,FIRE,FIRE,SMALL}
    };
    //定义二维数组,保存对应的动作(分数变更)
    private static final int[][] actionTable={
        {100,200,300,0},
        {0,200,300,-100},
        {0,0,0,-200},
        {0,0,0,-300}
    };
    
    
    //构造函数,定义初始分数和状态
    public MarioStateMachine(){
        this.score=0;
        this.currentState = State.SMALL;
    }

    //定义四个事件方法,方法中直接包含事件触发后的动作逻辑
    
    //事件E1 吃到蘑菇
    public void obtainMushRoom(){
        
    }
    //事件E2 获得斗篷
    public void obtainCape(){
        
    }
    //事件E3 获得火焰
    public void obtainFireFlower(){
        
    }
    //事件E4 遇到怪物 这里举例,只填充该方法内容
    public void ontainMonster(){
      
    }

    //根据状态表执行状态转移和动作
    private void executeEvent(Event event){
        int stateValue=currentState.getvalue;
        int eventValue=event.getValue;
        //当前状态确定某一行,事件确定某一列,交点为转以后得状态。
        this.currentState=transitionTable[stateValue][eventValue];
        this.score+=actionTable[stateValue][eventValue];
    }
    
    
    public int getScore(){
        return score;
    }
    public State getCurrentState(){
        return state;
    }
}

状态模式实现状态机

查表法实现状态机中,事件的动作只是简单地积分加减,所以用一个二维数组就能保存所有动作。但是业务中往往是一系列复杂的操作,比如操作数据库、缓存、远程接口等。此时查表法就有一定的局限性了。此时状态模式就可以排上用场。
状态模式将不同事件出发的状态和动作拆分到不同的状态类中,来避免分支语句。使用状态模式实现的代码如下

//marios所有的状态进行抽象,抽象方法为所有的事件。
public interface IMario{
    State getName();
    void obtainMushRoom();
    void obtainCape();
    void obtainFire();
    void meetMonster();
}

//为每个状态的IMario单独创建实现类,不在状态机中保存状态转换的逻辑;其他状态省略
public class SmallMario implements IMario{
    private MarioStateMachine stateMachine;  
    
    @Override
    public State getName(){
        return State.SMALL;
    }
    
    // SMALL状态下的马里奥,对吃到蘑菇事件的动作逻辑方法
    @Override
    public void obtainMushRoom(){
        statemMachine.setCurrentState(new SuperMario(stateMachine));
        stateMachine.setScore(stateMachine.getScore()+200);
    }
    
    //其他方法省略
    ...
    
}


//状态机
public class MarioStateMachine {
    private int score;
    private IMario currentState;//使用具体的IMario对象保存状态,而不是枚举

    //构造函数,定义初始分数和状态
    public MarioStateMachine(){
        this.score=0;
        this.currentState = new SmallMario(this);
    }

    //状态机事件
    public void obtainMushRoom(){
        //具体的动作逻辑,交由IMario对象实现
        this.currentState.obtainMushRoom();
    }

    //其他方法省略
    .....
}

  1. 可以看到,状态机和各个状态类是双向依赖关系。原因是状态机依赖状态类,是因为要保存当前状态。而状态类保存状态机对象,则是因为要更新状态机的数据。
  2. 可优化的点:状态类不保存任何数据,可以优化成单例模式,对于状态机对象直接作为方法参数进行传递。优化成单例模式代码如下。
//状态接口,定义的事件方法
public interface IMario{
    State getName();
    void obtainMushRoom( MarioStateMachine statemachine);
    void obtainCape( MarioStateMachine statemachine);
    void obtainFire( MarioStateMachine statemachine);
    void meetMonster( MarioStateMachine statemachine);
}


//为每个状态的IMario单独创建实现类,不在状态机中保存状态转换的逻辑;其他状态省略
public class SmallMario implements IMario{
    
    @Override
    public State getName(){
        return State.SMALL;
    }
    
    // SMALL状态下的马里奥,对吃到蘑菇事件的动作逻辑方法
    @Override
    public void obtainMushRoom(MarioStateMachine statemachine){
        statemachine.setCurrentState(new SuperMario(stateMachine));
        statemachine.setScore(stateMachine.getScore()+200);
    }
    
    //其他方法省略
    ...
    
}

Spring 状态机组件

Spring的状态机组件(Spring StateMachine)是Spring框架提供的一种用于实现有限状态机(Finite State Machine, FSM)模式的模块。

三种状态机的总结

对于游戏场景,包含非常多复杂的状态引入状态模式会导致非常多的状态类,所以推荐查表法。
对于电商订单、外卖订单等状态不多,状态转移简单但是动作复杂的场景,更推荐使用状态模式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/692758.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能与能源约束的矛盾能否化解

以下文章来源:澎湃新闻 人工智能技术在台前展示的是比特世界的算力、算法和数据,但其“轻盈的灵魂”背后则是土地、能源和水等物理世界“沉重的肉身”。根据本文三种情境的模拟测算,未来人工智能发展需要可持续的巨量能源支撑,能源…

DS:树与二叉树的相关概念

欢迎来到Harper.Lee的学习世界!博主主页传送门:Harper.Lee的博客主页想要一起进步的uu可以来后台找我哦! 一、树的概念及其结构 1.1 树的概念亲缘关系 树是一种非线性的数据结构,它是由n(n>0)个有限节点…

社区物资交易互助平台的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,基础数据管理,论坛管理,公告信息管理 前台账户功能包括:系统首页,个人中心,论坛,求助留言板,公…

OpenCV 双目相机标定

文章目录 一、简介1.1单目相机标定1.2双目相机标定二、实现代码三、实现效果参考资料一、简介 1.1单目相机标定 与单目相机标定类似,双目标定的目的也是要找到从世界坐标转换为图像坐标所用到的投影P矩阵各个系数(即相机的内参与外参)。具体过程如下所述: 1、首先我们需要…

王学岗鸿蒙开发(北向)——————(四、五、六)ArkUi声明式组件

普通组件 1,注意,如上图,build只能有一个根节点 2,Entry表示程序的入口 Component表示自定义的组件 Preview表示可以预览 3,图片存放的地方 4, Image组件最好只给宽度,给了高度又给宽度容易失真。 build() {Row() {/…

论文降痕指南:如何有效降低AIGC率

随着 AI 技术迅猛发展,各种AI辅助论文写作的工具层出不穷! 为了防止有人利用AI工具进行论文代写,在最新的学位法中已经明确规定“已经获得学位者,在获得该学位过程中如有人工智能代写等学术不端行为,经学位评定委员会…

使用 ESP32 和 PlatformIO (arduino框架)实现 Over-the-Air(OTA)固件更新

使用 ESP32 和 PlatformIO 实现 Over-the-Air(OTA)固件更新 摘要: 本文将介绍如何在 ESP32 上使用 PlatformIO 环境实现 OTA(Over-the-Air)固件更新。OTA 更新使得在设备部署在远程位置时,无需物理接触设…

Python 基于阿里云的OSS对象存储服务实现本地文件上云框架

Python 基于阿里云的OSS对象存储服务实现将文件上云框架 文章目录 Python 基于阿里云的OSS对象存储服务实现将文件上云框架一、前言二、阿里云配置1、获取用户AKEY和AKeySecret2、创建Bucket 三、Python 阿里云oss上云框架1、安装oss2依赖库2、阿里云oss python 一、前言 未来…

Mysql 中的case-when

什么是 case-when case-when 是一种 sql 语句中的语法结构,结构如下: case 字段名 when 值 then 字段名|值 ... else 字段名|值 end case when 主要用于数据的 行列转换(把一列数据转换为多列) 前置条件: -- 表…

基于统一二维电子气密度表达式的通用MIS-HEMT紧凑模型

来源:A Compact Model for Generic MIS-HEMTs Based on the Unified 2DEG Density Expression(TED 14年) 摘要 本文提出了一种针对二维电子气(ns)密度和费米能级(E_f)的解析表达式&#xff0c…

【Pytorch】计算机视觉项目——卷积神经网络TinyVGG模型图像分类(如何使用自定义数据集)

目录 一、前言二、工作流程回顾三、详细步骤流程1. 环境配置2. 数据准备数据集下载数据存储结构&路径查看图片 3. 数据转换4. 自定义数据集(Custom Dataset )4.1 方法一:使用ImageFolder加载数据集信息查看张量转图片创建DataLoader 4.2 …

使用Python创建Word文档

使用Python创建Word文档 安装python-docx库创建Word文档代码效果 在这篇文章中,我们将介绍如何使用 Python创建一个Word文档。首先,我们需要安装python-docx库,然后通过一段简单的代码示例展示如何创建和编辑Word文档。 安装python-docx库 …

【Linux】进程(9):进程控制1

大家好,我是苏貝,本篇博客带大家了解Linux进程(9)进程控制1,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 目录 1 fork函数2 进程终止(A)终止是…

设计模式-策略模式(行为型)

行为型-策略模式 了解策略模式 策略模式是一种行为型设计模式,在策略模式中定义了一系列算法或者策略,并将这些策略封装到独立的类中,使得可以相互替换。在使用时,可以指定响应的策略使用。 角色 策略接口:对于某种…

免费使用GPT-4生成图片的方法

写在前言 hello,大家好,我是一点,喜欢编程,目前持续在关注AI领域的发展,希望可以结交一些有意思的朋友。 大家可以关注我的公号:【一点sir】,了解更多文章,希望可以和你们成为朋友…

10 设备树

掌握设备树是 Linux 驱动开发人员必备的技能! 1、什么是设备树 新版本 Linux 中,ARM 相关的驱动全部采用了设备树。Linux-4.1.15 支持设备树。我们了解一下设备树的起源、重点学习一下设备树语法。 设备树:Device Tree,就是“设备”和“树”,描述设备树的文件叫做 DTS(…

Ubuntu硬盘分区、挂载、修改用户权限

使用命令查看硬盘情况 sudo fdisk -l 可以看到这里有个未分区的4T硬盘 如:sdb 这样的是硬盘 sdb1 sdb2 这样的是分区,现在还没分区 分区 sudo parted /dev/sdb (sdb 是要挂载的硬盘) 输入一下命令分区: mklabel gpt (创建分区表) mkpart p…

天工开物 #14 分析时序数据:从 InfluxQL 到 SQL 的演变

近年来,时序数据的增长是 Data Infra 领域一个不容忽视的趋势。这主要得益于万物互联带来的自然时序数据增长,以及软件应用上云和自身复杂化后的可观测性需求。前者可以认为是对联网设备的可观测性,而可观测性主要就建构在设备或应用不断上报…

172.二叉树:左叶子之和(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr, right(nullptr) {}* Tree…

深入理解C语言:main函数的奥秘

在C语言中,main函数是每个程序的入口点,起着至关重要的作用。本文将深入探讨main函数的工作原理,包括其参数、返回值、以及如何从main启动程序的执行。通过实际代码示例,读者将更深入地理解main函数在C语言编程中的核心地位。 第一…