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

news2024/12/25 12:16:42

什么是有限状态机

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)模式的模块。

三种状态机的总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【机器人和人工智能——自主巡航赛项】进阶篇

文章目录 案例要求创建地图rviz仿真 保存地图坐标点定位识别训练主逻辑理解语音播报模块匹配二维码识别多点导航讲解视频其余篇章 案例要求 创建地图 ./1-gmapping.sh 把多个launch文件融合在sh文件里面 rviz仿真 rviz是rose集成的可视化界面,查看机器人的各项数…

html+CSS+js部分基础运用17

在图书列表中,为书名“零基础学JavaScript”和“HTML5CSS3精彩编程200例”添加颜色。(请用class或style属性实现),效果如下图1所示: 图1 图书列表 Class和style的综合应用。(1)应用class的对象、…

CNN简介与实现

CNN简介与实现 导语整体结构卷积层卷积填充步幅三维卷积立体化批处理 实现 池化层特点实现 CNN实现可视化总结参考文献 导语 CNN全称卷积神经网络,可谓声名远扬,被用于生活中的各个领域,也是最好理解的神经网络结构之一。 整体结构 相较于…

Servlet-01

文章目录 Servlet创建Servlet探究Servlet的生命周期 HttpServletWebServlet注解详解 重定向与请求转发ServletContextServletContext中的接口 HttpServletRequestHttpServletResponse状态码解释Cookie Servlet Q:它能做什么呢? A:我们可以通…

使用汇编和proteus实现仿真数码管显示电路

proteus介绍: proteus是一个十分便捷的用于电路仿真的软件,可以用于实现电路的设计、仿真、调试等。并且可以在对应的代码编辑区域,使用代码实现电路功能的仿真。 汇编语言介绍: 百度百科介绍如下: 汇编语言是培养…

1-5 C语言操作符

C语言提供了非常丰富的操作符,使得C语言使用起来非常的方便 算数操作符: 加 减 乘 除 取模 【 - * / %】 注:除号的两端都是整数的时候执行的是整数的除法,如果…

Unity 编辑器扩展,获取目录下所有的预制件

先看演示效果 实现方案 1创建几个用于测试的cube 2,创建一个Editor脚本 3,编写脚本内容 附上源码 using UnityEditor; using UnityEngine;public class GetPrefeb : EditorWindow {private string folderPath "Assets/Resources"; // 指定预…

【Python】数据处理:文本文件操作

在Python中,处理文本文件是非常常见的任务。可以使用内置的open函数来打开、读取和写入文本文件。 打开文件 使用open函数打开文件。该函数有两个主要参数: open(file, moder, buffering-1, encodingNone, errorsNone, newlineNone, closefdTrue, ope…

ssm602社区医疗保健监控系统+vue【以测试】

前言:👩‍💻 计算机行业的同仁们,大家好!作为专注于Java领域多年的开发者,我非常理解实践案例的重要性。以下是一些我认为有助于提升你们技能的资源: 👩‍💻 SpringBoot…

【设计模式】行为型设计模式之 策略模式学习实践

介绍 策略模式(Strategy),就是⼀个问题有多种解决⽅案,选择其中的⼀种使⽤,这种情况下我们 使⽤策略模式来实现灵活地选择,也能够⽅便地增加新的解决⽅案。⽐如做数学题,⼀个问题的 解法可能有…

Linux shell编程基础

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务。 Shell 脚本&#x…

GQA,MLA之外的另一种KV Cache压缩方式:动态内存压缩(DMC)

0x0. 前言 在openreview上看到最近NV的一个KV Cache压缩工作:https://openreview.net/pdf?idtDRYrAkOB7 ,感觉思路还是有一些意思的,所以这里就分享一下。 简单来说就是paper提出通过一种特殊的方式continue train一下原始的大模型&#x…

打破 AIGC 算力困境,io.net 如何实现“GPU 互联网”?

AIGC 在全球快速发展的当下,诸多项目深陷 GPU 运力不足,速度放缓、任务宕机、项目崩溃等困境,作为瞄准 AI 理念和 DePIN 赛道的 Solana 生态项目新星 io.net 来说,如何集成项目控制与云计算服务成为抢占市场的重要发力方向。第 11…

将web项目打包成electron桌面端教程(一)vue3+vite+js

说明:后续项目需要web端和桌面端,为了提高开发效率,准备直接将web端的代码打包成桌面端,在此提前记录一下demo打包的过程,需要注意的是vue2或者vue3的打包方式各不同,如果你的项目不是vue3vitejs&#xff0…

Nagios的安装和使用

*实验* *nagios安装和使用* Nagios 是一个监视系统运行状态和网络信息的监视系统。Nagios 能监视所指定的本地或远程主机以及服务,同时提供异常通知功能等. Nagios 可运行在 Linux/Unix 平台之上,同时提供一个可选的基于浏览器的 WEB 界面以方便系统管…

go语言后端开发学习(二)——基于七牛云实现的资源上传模块

前言 在之前的文章中我介绍过我们基于gin框架怎么实现本地上传图片和文本这类的文件资源(具体文章可以参考gin框架学习笔记(二) ——相关数据与文件的响应),但是在我们实际上的项目开发中一般却是不会使用本地上传资源的方式来上传的,因为文件的上传与读…

项目验收总体计划书(实际项目验收原件参考Word)

测试目标:确保项目的需求分析说明书中的所有功能需求都已实现,且能正常运行;确保项目的业务流程符合用户和产品设计要求;确保项目的界面美观、风格一致、易学习、易操作、易理解。 软件全套文档过去进主页。 一、 前言 &#xff0…

图鸟UI-Icon演示:探索多功能前端模板的魅力

在当今数字化的时代,用户界面(UI)设计在提升用户体验方面扮演着至关重要的角色。随着技术的不断进步,开发者们对于高效、统一且美观的UI组件需求日益增加。图鸟UI,作为一款功能强大且灵活的UI框架,正满足了…

一款免费文件夹同步工具,旨在帮助用户在不同磁盘或文件夹间进行文件和目录的复制、移动和同步工作

一、简介 1、一款免费文件夹同步工具,旨在帮助用户在不同磁盘或文件夹间进行文件和目录的复制、移动和同步工作。这款工具因其简单易用、高度可定制化的特点,受到了广大用户的青睐。SyncToy支持多种同步模式,包括镜像同步、单向同步以及增量同…

第四篇红队笔记-百靶精讲之Prime-wfuzz-wpscan-openssl enc

靶机Prime渗透 主机发现 nmap扫描与分析 目录爆破与模糊测试 dirb 目录扫描 dev secret.txt wfuzz发现 file参数 根据secret.txt-location.txt 和 file参数结合 secrettier360 根据filelocation.txt得到的on some other php page(改用之前扫到image.p…