当前位置: 首页 > news >正文

设计模式-状态模式 golang实现

一 什么是有限状态机 

有限状态机,英⽂翻译是 Finite State Machine,缩写为 FSM,简称为状态机。

状态机不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。

已订单交易为例:

1.1四大概念

下面来给出状态机的四大概念。

  1. State ,状态。一个状态机至少要包含两个状态。例如上商家交易有 已下单、已支付、已发货等多种状态。
  2. Event,事件。事件也称为转移条件(Transition Condition)。例如 客户下单、 客户完成支付、商家发货 都是一个事件。
  3. Action ,动作。事件发生以后要执行动作。例如用户支付,扣减用户余额就是动作。编程的时候,一个 Action 一般就对应一个函数。不过动作不是必须的,也可能只转移状态,不执⾏任何动作。
  4. Transition ,变换。也就是从一个状态变化为另一个状态。例如 订单从“已支付”转换到“已发货”。

二 状态机的实现方法

将上面业务流程翻译成骨架代码:


type State int64const StateWaitingPayment State = 1 //等待支付
const StateWaitingShip State = 2    //支付成功待发货// 订单状态机
type LeaseStateMachine struct {State State //订单状态
}// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {//todo
}// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {//todo
}// 商家发货
func (p *LeaseStateMachine) EventShipped() {//todo
}// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {//todo
}

2.1 分支逻辑

最简单直接的实现⽅式是,参照状态转移 图,将每⼀个状态转移,直译成代码。这样编写的代码会包含⼤量的 if-else 或 switch-case 分⽀判断逻辑。

type State int64const StateWaitingPayment State = 1      //等待支付
const StateWaitingShip State = 2         //支付成功待发货
const StateWaitingShipped State = 3      //发货成功
const StateWaitingOrderSuccess State = 4 //订单结束
const StateWaitingOrderCancel State = 5  //订单取消// 租赁订单状态机
type LeaseStateMachine struct {State State //订单状态
}// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {if p.State == StateWaitingPayment {p.State = StateWaitingShip}
}// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {if p.State == StateWaitingShip ||p.State == StateWaitingPayment {p.State = StateWaitingOrderCancel}
}// 商家发货
func (p *LeaseStateMachine) EventShipped() {if p.State == StateWaitingShip {p.State = StateWaitingShipped}
}// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {if p.State == StateWaitingShipped {p.State = StateWaitingOrderSuccess}
}

2.2 查表法

除了⽤状态转移图来表示之外,状态机还可以⽤⼆维表来表示;将上面的状态图转换成二维表如下

当前状态/事件

E支付成功

E发货E取消订单E确认收货
等待支付支付成功待发货///
支付成功待发货/发货成功订单取消/
已发货///订单结束
订单结束////
订单取消////

 

使用查表表修改上述代码:
 


type State int64const StateWaitingPayment State = 1      //等待支付
const StateWaitingShip State = 2         //支付成功待发货
const StateWaitingShipped State = 3      //发货成功
const StateWaitingOrderSuccess State = 4 //订单结束
const StateWaitingOrderCancel State = 5  //订单取消type Event int64const (EventPay            Event = 1 //支付事件EventShip           Event = 2 //发货 事件EventCancel         Event = 3 //取消订单 事件EventConfirmReceipt Event = 4 //确认收货
)// 状态二维表配置
var StateTable map[State]map[Event]State = map[State]map[Event]State{StateWaitingPayment: {EventPay: StateWaitingShip, //待支付订单 ,支付事件 => 已支付},StateWaitingShip: {EventShip:   StateWaitingShipped,EventCancel: StateWaitingOrderCancel,},//.......
}// 租赁订单状态机
type LeaseStateMachine struct {State State //订单状态}// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {p.ExecEventConfirmReceipt(EventPay)
}// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {p.ExecEventConfirmReceipt(EventCancel)
}// 商家发货
func (p *LeaseStateMachine) EventShipped() {p.ExecEventConfirmReceipt(EventShip)
}// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {p.ExecEventConfirmReceipt(EventConfirmReceipt)
}// 执行事件
func (p *LeaseStateMachine) ExecEventConfirmReceipt(event Event) {EventNewStateTable, ok := StateTable[p.State]if ok {newState, ok := EventNewStateTable[event]if ok {p.State = newState}}
}

在查表法的代码实现中,事件触发的动作只是简单状态变换,所以⽤⼀个 int 类型 的⼆维数组 actionTable 就能表示。但是,如果要执⾏ 动作并⾮这么简单,⽽是⼀系列复杂的逻辑操作(⽐如加减积分、写数据库,还有可能发 送消息通知等等),我们就没法⽤如此简单的⼆维数组来表示了。

2.3状态模式

状态模式通过将事件触发的状态转移和动作执⾏,拆分到不同的状态类中,来避免分⽀判断

逻辑。

1.定义interface 所有事件

type ILeaseState interface {//定义事件EventPay() //支付事件EventShip() //发货事件EventCancel() //取消订单事件EventConfirmReceipt() //确认收货事件
}

2.状态类实现 事件对应的action

将事件对饮的代码逻辑被分散到各个状态类中。

//==================================================================
// 待支付状态
type StateWaitingPaymentImp struct{}// 订单支付成功
func (p *StateWaitingPaymentImp) EventPay() {//todo 更新订单状态
}// 发货
func (p *StateWaitingPaymentImp) EventShip() {//不做处理
}// 取消
func (p *StateWaitingPaymentImp) EventCancel() {//todo 取消
}// 确认收货事件
func (p *StateWaitingPaymentImp) EventConfirmReceipt() {//不做处理
}
//==================================================================
// 支付成功 状态
type StateWaitingShipImp struct{}// 订单支付成功
func (p *StateWaitingShipImp) EventPay() {//不做任何处理
}// 发货
func (p *StateWaitingShipImp) EventShip() {//更新订单未发货
}// 取消
func (p *StateWaitingShipImp) EventCancel() {//更新订单未发货
}// 确认收货事件
func (p *StateWaitingShipImp) EventConfirmReceipt() {//不做处理
}
//===============================================================
//........其他状态对应的事件

三 总结

实现方法对比

实现方法优点缺点
分支逻辑
  • 简单、直接,易理解。
  • 对简单的状态机首选该方法实现。

  • 对于复杂的状态机来说,代码中充斥着⼤量的 ifelse 或者 switch-case 分⽀判断逻辑,可读性和可维护性差。

    易漏写或者错写某个状态转移。
    如果哪天修改了状态机 中的某个状态转移,我们要在冗⻓的分⽀逻辑中找到对应的代码进⾏修改,很容易改错,导致 bug。
 
查表法
  • 查表法的代码实现更加清晰,可读性和可维护性更好。
  • 当修改 状态机时,只需要修改 transitionTable 和 actionTable 状态转移配置
     
  • 查表法的实现⽅式有⼀定局限性,
    执行的action只能是简单的状态转移操作。

    如果要执⾏的action是⼀系列复杂的逻辑操作(⽐如加减积分、写数据库,还有可能发送消息通知等等),我们就没法⽤如此简单的⼆维数组来表示了。
状态模式
 
  • 对于状态并不多、状态转移也⽐较简单,但事件触发执⾏的action包含的业务逻辑可能⽐较复杂的状态机来说,⾸选状态模式
 
  • 状态模式会引⼊⾮常多的状态类,会导致代码⽐较难维护

像电商下单这种状态并不多,状态转移也⽐较简单,但事件触发执⾏的动作包含的业务逻辑可能会⽐较复杂,更加推荐使⽤状态模式来实现。

像游戏⽐较复杂的状态机,包含的状态⽐较多,优先推荐使⽤查表法,

相关文章:

设计模式-状态模式 golang实现

一 什么是有限状态机 有限状态机,英⽂翻译是 Finite State Machine,缩写为 FSM,简称为状态机。 状态机不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。 已订单交易为例: 1.…...

通过docker快速部署RabbitMq

查询镜像: docker search rabbitmq拉去RabbitMq镜像: docker pull rabbitmq:management创建数据卷: docker volume create rabbitmq-home运行容器: docker run -id --namerabbitmq -v rabbitmq-home:/var/lib/rabbitmq -p 156…...

Spring Boot 配置文件中的中文读取出来是乱码,或者是问号

在调试阿里短信时候,遇到读取配置文件乱码导致标签名无法正常使用,而可能有两个原因导致这个问题,一个是配置文件编码方式不是UTF-8的,另一个是Spring http使用的编码不是UTF-8。 1、第一步,将配置文件的编码方式改为U…...

【系统架构设计】架构核心知识: 3.8 ADL和产品线

目录 一 ADL 二 产品线 1 产品线 2 过程模型 3 软件产品线的建立方式...

imagettftext(): Could not find/open font 解决办法

问题:Captcha验证码不能正常显示,是因为使用GD库imagettftext()函数时,报“Warning: imagettftext(): Could not find/open font in ”警告 。 网上的解决方法: 将font路径的相对路径 转成 绝对路径即可 $fontfile "./fonts/*.ttf&q…...

P1853 投资的最大效益(DP背包)

投资的最大效益 题目背景 约翰先生获得了一大笔遗产,他暂时还用不上这一笔钱,他决定进行投资以获得更大的效益。银行工作人员向他提供了多种债券,每一种债券都能在固定的投资后,提供稳定的年利息。当然,每一种债券的…...

LightDB23.4 支持普通表修改为list分区表

功能介绍 为了兼容Oracle数据库的功能,在LightDB23.4版本上支持修改普通表为List分区表。这个功能只在LightDB的Oracle兼容模式下生效。 使用示例 进入Oracle兼容模式的数据库 lightdboracle_test# show lightdb_dblevel_syntax_compatible_type ;lightdb_dblev…...

Java序列化和Json格式的转化

Java序列化和JSON格式的转换都是在不同格式之间实现对象的传输,并在数据节点之间方便地进行信息交换,其中主要区别在于它们的工作原理和应用场景。 Java序列化是将 Java 对象转换为字节流(二进制格式的数据),以便在网…...

ElementUI之el-progress动态修改进度条里面文本颜色与进度条色块统一

1.效果&#xff1a; 2.实现方式 通过行内style样式动态给整个progress赋颜色 再在样式里给进度条文字单独设置颜色为默认继承父级颜色就ok啦 <el-progress class"custom-progress" stroke-linecap"square" :style"{color:item.color}" :colo…...

elementUI的el-menu组件做内部组件和外链区分

场景&#xff1a;左侧菜单栏的菜单项有内部组件切换&#xff0c;也会有点击后进入外链的情况&#xff0c;如何同时处理这种情况&#xff1f; 解决思路&#xff1a; 在路由配置中path代表组件切换路径或者外链配置el-menu-item显示菜单项时&#xff0c;使用动态路由形式&#…...

使用Ruby编写通用爬虫程序

目录 一、引言 二、环境准备 三、爬虫程序设计 1. 抓取网页内容 2. 解析HTML内容 3. 提取特定信息 4. 数据存储 四、优化和扩展 五、结语 一、引言 网络爬虫是一种自动抓取互联网信息的程序。它们按照一定的规则和算法&#xff0c;遍历网页并提取所需的信息。使用Rub…...

231108 C语言中是否可以函数内部动态申请内存,再传给外部变量?

如题。 是否可以返回一个指针&#xff0c;这个指针是函数内部动态申请内存的起始地址&#xff1f; 自然&#xff0c;内部动态申请内存在函数执行结束时是需要销毁的。那么是否可以在销毁前将指针赋值给函数返回值&#xff1f;当然&#xff0c;函数返回值是一个同类型指针。...

基于飞迪RTK/INS组合导航模组的里程计发布方法

文章目录 概要解算过程获取初始化点经纬度坐标系转UTM计算航向角发布odom坐标 完整代码 概要 这篇博客主要介绍&#xff0c;如何将GPS_fix、磁偏角转成odom信息。 PS:官方的驱动包中是自带odom信息&#xff0c;但是对于原点的定义尚未找到出处&#xff0c;故自己另外写了一套发…...

无mac电脑获取app的公钥的方法

在腾讯云或阿里云进行ios的app备案的时候&#xff0c;它要求输入app的公钥 但是他们并没有提供mac电脑的获取工具&#xff0c;需要我们使用mac电脑去获取app的公钥 假如我们没有mac电脑怎么办呢&#xff1f; 网上很多教程是通过java代码去获取的&#xff0c;太麻烦了&#x…...

【Mybatis源码】反射 – TypeParameterResolver

反射在Java编程开发中具有很重要的地位,能够使用反射机制创建实例、获取或设置字段的值、调用方法等,但如果字段、方法中出现泛型类型时,我们在使用反射进行解析时,往往不能解析到实际的类型,只能解析到泛型参数。 在Mybatis中使用TypeParameterResovler类提供了对Type的封…...

Drogon源码剖析

一、Drogon介绍 Drogon是一个基于C的跨平台HTTP应用程序框架&#xff0c;它支持Linux&#xff0c;也支持macOS、FreeBSD&#xff0c;OpenBSD&#xff0c;HaikuOS&#xff0c;和Windows。项目地址&#xff1a;https://github.com/drogonframework/drogon。 它的主要特点如下&a…...

maven 上传本地jar包到nexus

maven上传命令 mvn deploy:deploy-file -DgroupIdcom.microsoft.sqlserver -DartifactIdsqljdbc4 -Dversion4.0 -Dpackagingjar -DfileC:\java\top-sdk-java-1.0.1-lib\lib\bcprov-jdk16-1.46.jar -Durlhttp://ip:port/repository/maven-releases/ -DrepositoryIdsnapshot…...

聊一聊,今年参加软考高级的一些总结

先上结论&#xff0c;系统架构设计师考题难度不高&#xff0c;总之多读书&#xff0c;多刷题&#xff0c;多写博客&#xff0c;多总结&#xff0c;有一定工作经验的基本上都非常容易过。但是我估计自己考不过&#xff0c;主要是论文这块没写好&#xff0c;思路不清晰&#xff0…...

【寒武纪(4)】图像处理硬件加速,基于CNCVE

基本概念 1、handle 句柄标识不同任务 2、对于调用上&#xff0c;支持阻塞和非阻塞。使用bInstant标识。 3、查询query可以确认调用是否完成 4、及时刷新cache。CNCVE 硬件的唯一数据来源是DDR&#xff0c;防止CPU访问导致cache内存干扰&#xff0c;需要调用cnsysMacheOperate…...

有关python库

官方库 #1、导入某模块 import os #2、导入OS模块中的system方法 from os import system #3、导入某模块中的孙子模块中的xx方法&#xff0c;并重命名 from module.xx.xx import xx as rename #4、导入OS中的所有模块 #不用进行OS.method(),直接method&#xff08;&#xff0…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

毫米波雷达基础理论(3D+4D)

3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文&#xff1a; 一文入门汽车毫米波雷达基本原理 &#xff1a;https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下&#xff0c;大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性&#xff0c;吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型&#xff0c;成为释放其巨大潜力的关键所在&…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章

用 Rust 重写 Linux 内核模块实战&#xff1a;迈向安全内核的新篇章 ​​摘要&#xff1a;​​ 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言&#xff0c;受限于 C 语言本身的内存安全和并发安全问题&#xff0c;开发复杂模块极易引入难以…...

【记录坑点问题】IDEA运行:maven-resources-production:XX: OOM: Java heap space

问题&#xff1a;IDEA出现maven-resources-production:operation-service: java.lang.OutOfMemoryError: Java heap space 解决方案&#xff1a;将编译的堆内存增加一点 位置&#xff1a;设置setting-》构建菜单build-》编译器Complier...