【设计模式】行为型设计模式之 状态模式,带你探究有限状态机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;  @Overridepublic State getName(){return State.SMALL;}// SMALL状态下的马里奥,对吃到蘑菇事件的动作逻辑方法@Overridepublic 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{@Overridepublic State getName(){return State.SMALL;}// SMALL状态下的马里奥,对吃到蘑菇事件的动作逻辑方法@Overridepublic 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.mzph.cn/diannao/25081.shtml

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

相关文章

全链路性能测试:Nginx 负载均衡的性能分析和调优

为什么性能测试很多同学觉得是一个比较难以自学上岸的测试领域,是因为真正做全链路的性能测试是比较难的。所谓的全链路就是在项目的整个链路上任何一环节都有可能存在性能测试瓶颈,我们都需要能够通过分析性能的监控指标找到对应的问题。 我们今天要讲的Nginx负载均衡就是…

C++中的一些困惑(长期更新中)

C中的一些困惑 文章目录 C中的一些困惑1. using std::具体命名与using namespace std;2. 【int \*p[10] 】与 【int (\*p)[10]】3. main()函数可带参,参从何来?4. constexpr函数的返回值可不为常量,那这时constexpr关键字作用是什么&#xff…

网页中生成ZIP文件,Zip 压缩、解压技术在 HTML5 浏览器中的应用

JSZip 是一款可以创建、读取、修改 .zip 文件的 javaScript 工具。在 web 应用中,免不了需要从 web 服务器中获取资源,如果可以将所有的资源都合并到一个 .zip 文件中,这时候只需要做一次请求,这样既减少了服务器的压力&#xff0…

Python:处理矩阵之NumPy库(上)

目录 1.前言 2.Python中打开文件操作 3.初步认识NumPy库 4.使用NumPy库 5.NumPy库中的维度 6.array函数 7.arange函数 8.linspace函数 9.logspace函数 10.zeros函数 11.eye函数 前言 NumPy库是一个开源的Python科学计算库,它提供了高性能的多维数组对象、派生对…

11-数组与指针深入理解——题型理解

11-数组与指针深入理解——题型理解 文章目录 11-数组与指针深入理解——题型理解一、理解题1二、理解题二三、理解题三四、理解题四五、理解题五六、理解题六 一、理解题1 #include <stdio.h>int main(void) {int (*p)[5] NULL; // 定义一个指向 拥有5个整型数据的数组…

【Java】JDBC+Servlet+JSP实现搜索数据和页面数据呈现

目录 1 .功能介绍 2. 实现流程 3. 项目环境 4. 相关代码 4.1 Maven配置 4.2 SQL语句 4.3 Java代码 4.4 HTML代码 4.5 JSP代码 5. 结果展示 &#xff08;原创文章&#xff0c;转载请注明出处&#xff09; 博主是计算机专业大学生&#xff0c;不定期更新原创优质文章&…

Android 常用开源库 MMKV 源码分析与理解

文章目录 前言一、MMKV简介1.mmap2.protobuf 二、MMKV 源码详解1.MMKV初始化2.MMKV对象获取3.文件摘要的映射4.loadFromFile 从文件加载数据5.数据写入6.内存重整7.数据读取8.数据删除9.文件回写10.Protobuf 实现1.序列化2.反序列化 12.文件锁1.加锁2.解锁 13.状态同步 总结参考…

gitlabcicd-k8s部署runner

一.环境信息 存储使用nfs挂载持久化 k8s环境 helm安装 建议helm 3 二.部署gitlab-runner 1.查看gitlab版本 进入容器可通过执行&#xff1a;gitlab-rake gitlab:env:info rootgitlab-647f4bd8b4-qz2j9:/# gitlab-rake gitlab:env:info System information System: Current Us…

【面试干货】 Hash 索引和 B+树索引的区别

【面试干货】 Hash 索引和 B树索引的区别 1、Hash 索引2、B 树索引3、区别和适用场景 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在数据库中&#xff0c;索引是一种重要的数据结构&#xff0c;用于加速查询操作。常见的索引包括 Hash 索…

蓝桥杯--跑步计划

问题描述 小蓝计划在某天的日期中出现 11 时跑 55 千米&#xff0c;否则只跑 11 千米。注意日期中出现 11 不仅指年月日也指星期。 请问按照小蓝的计划&#xff0c;20232023 年小蓝总共会跑步锻炼多少千米?例如&#xff0c;55 月 11 日、11 月 1313 日、1111 月 55 日、44 月…

从零开始实现自己的串口调试助手(10) - 优化 收尾 + 打包

光标位置优化 在接收槽函数中更新光标位置: // 让光标始终在结尾 ui->textEditRev->moveCursor(QTextCursor::End); ui->textEditRev->ensureCursorVisible(); // 让光标可视化 //记得HEX显示槽函数底下也得加上这两行代码 新的接收槽函数如下: void Wid…

一维信号循环平移小波降噪方法(MATLAB R2021b)

循环平移算法由Coifman和Donoho最先提出&#xff0c;其基本原理是将信号进行循环平移&#xff0c;将平移后的信号降噪后再做逆循环平移&#xff0c;改变平移位数&#xff0c;多次重复上述运算&#xff0c;将获得的所有结果求平均&#xff0c;得到最后的结果。 在理想情况下&am…

Java面试八股之什么是自动装箱和自动拆箱

什么是自动装箱和自动拆箱 在Java中&#xff0c;自动装箱&#xff08;Autoboxing&#xff09;和自动拆箱&#xff08;Auto-unboxing&#xff09;是两个与基本数据类型和它们对应的包装类之间的转换相关的特性。这两个概念自Java 5&#xff08;也称为Java SE 5或JDK 5&#xff…

【CS.CN】深入探讨下HTTP的Connection头:通过keep-alive实现高效网络连接

文章目录 0 序言0.1 由来0.2 使用场景0.3 现在还需要吗&#xff1f; 1 Connection: keep-alive的机制2 语法 && 通过设置Connection: keep-alive优化性能3 验证与性能提升4 总结References 0 序言 0.1 由来 Connection头部字段在HTTP/1.1中被引入&#xff0c;主要用于…

老旧机子装linux——Xubuntu

目录 前言 正文 下载系统 ​编辑 制作系统盘&#xff1a; 安装界面 Xubuntu ​编辑 lubuntu 后语 前言 有两台电脑&#xff0c;一台装了Ubuntu22&#xff0c;一台装了debuntu。虽然debuntu界面与乌班图大体一样&#xff0c;但是编译器好像有点区别。由于机子为10年前的老…

React的useState的基础使用

import {useState} from react // 1.调用useState添加状态变量 // count 是新增的状态变量 // setCount 修改状态变量的方法 // 2.添加点击事件回调 // userState实现计数实例import {useState} from react// 使用组件 function App() {// 1.调用useState添加状态变量// coun…

2024年AI大模型训练数据白皮书作用

2024年AI大模型训练数据白皮书 在人工智能迅猛发展的今天&#xff0c;AI大模型的训练数据质量和管理成为影响其性能和应用效果的关键因素。《2024年AI大模型训练数据白皮书》为业内人士提供了一份详尽的指南&#xff0c;揭示了当前AI大模型训练数据的最新趋势、最佳实践以及未…

Go微服务: 基于rocketmq:server和rocketmq:broker搭建RocketMQ环境,以及生产消息和延迟消费消息的实现

RocketMQ 的搭建 1 ) 配置 docker-compose.yaml 文件 version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_name: rmqnamesrvports:- 9876:9876volumes:- ./logs:/opt/logs- ./store:/opt/storenetworks:rmq:aliases:- rmqnamesrvrmqbroker:image: fo…

[HGAME 2023 week4]shellcode

看题目&#xff0c;将base64解密&#xff0c;然后dump下来&#xff0c;再拉进ida里&#xff0c;发现为tea加密 在tea加密中得到key 密文就是另外的一个文件 exp import re from ctypes import *import libnumdef decrypt(v, k):v0, v1 c_uint32(v[0]), c_uint32(v[1])delta…

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

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