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

一 什么是有限状态机 

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

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

已订单交易为例:

1.1四大概念

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

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

二 状态机的实现方法

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


type State int64

const 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 int64

const 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 int64

const StateWaitingPayment State = 1      //等待支付
const StateWaitingShip State = 2         //支付成功待发货
const StateWaitingShipped State = 3      //发货成功
const StateWaitingOrderSuccess State = 4 //订单结束
const StateWaitingOrderCancel State = 5  //订单取消

type Event int64

const (
	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包含的业务逻辑可能⽐较复杂的状态机来说,⾸选状态模式
 
  • 状态模式会引⼊⾮常多的状态类,会导致代码⽐较难维护

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

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

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

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

相关文章

unity打AB包,AssetBundle预制体与图集(三)

警告: spriteatlasmanager.atlasrequested wasn’t listened to while 条件一:图片打图集里面去了 条件二:然后图集打成AB包了 条件三:UI预制体也打到AB包里面去了 步骤一:先加载了图集 步骤二:再加载UI预…

Spring Cloud LoadBalancer基础知识

LoadBalancer 概念常见的负载均衡策略使用随机选择的负载均衡策略创建随机选择负载均衡器配置 Nacos 权重负载均衡器创建 Nacos 负载均衡器配置 自定义负载均衡器(根据IP哈希策略选择)创建自定义负载均衡器封装自定义负载均衡器配置 缓存 概念 LoadBalancer(负载均衡器)是一种…

jenkins部署job

apt install fontconfig openjdk-11-jre wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/war/2.429/jenkins.wardeb包安装 wget https://mirrors.tuna.tsinghua.edu.cn/jenkins/debian-stable/jenkins_2.414.3_all.debdpkg -i jenkins_2.414.3_all.deb 访问 http://…

垂直领域大模型落地思考

相比能做很多事,但每件事都马马虎虎的通用大模型;只能做一两件事,但这一两件事都能做好,可被信赖的垂直大模型会更有价值。这样的垂直大模型能帮助我们真正解决问题,提高生产效率。 本文将系统介绍如何做一个垂直领域…

JavaScript脚本操作CSS

脚本化CSS就是使用JavaScript脚本操作CSS,配合HTML5、Ajax、jQuery等技术,可以设计出细腻、逼真的页面特效和交互行为,提升用户体验,如网页对象的显示/隐藏、定位、变形、运动等动态样式。 1、CSS脚本化基础 CSS样式有两种形式&…

学习笔记:CANOE模拟LIN主节点和实际从节点进行通信测试

先写点感想,在LIN开发阶段,我一般用图莫斯USB工具来进行模拟主机节点发送数据。后来公司买了CANOE工具就边学习边搭建了LIN的测试工程,网上的资料真的很少,主要是靠自己一点点摸索前进,总算入门。几个月后的今天&#…

基于swing的人事管理系统

概述 个人项目人事管理系统,针对部门和人员之间的管理。 详细 一、项目UI 二、项目结构 三、项目使用方法 Eclipse导入现有现有项目到工作空间即可,会自动加载包内相关jar包,使用的java源文件 四、部分代码 MainFrm.java package view…

Docker指定容器使用内存

Docker指定容器使用内存 作者:铁乐与猫 如果是还没有生成的容器,你可以从指定镜像生成容器时特意加上 run -m 256m 或 --memory-swap512m来限制。 -m操作指定的是物理内存,还有虚拟交换分区默认也会生成同样的大小,而–memory-…

ubuntu系统黑屏,且光标不闪烁

选择第二个,进入恢复模式 选择第二个,进入恢复模式 选择root 输入: startx然后就可以进入文本界面或者图形化界面了,如果不行,报错,可能需要需要下载这个包,把这个错误到网上搜索一下就可以找…

批量迁移redis实例的key

我们知道migrate 命令可以迁移redis的多个key,但是如果redis的key有非常多,那用起来就很不方便了。 所以下面分享一个脚本来实现批量key的迁移,主要使用的命令为dump和restore 脚本如下: #!/bin/bash redis-cli -h host1 -p 63…

Redis被攻击纪实

一、前言 声明:本文仅供技术交流使用,严禁采用本文的方法进行任何非法活动。 上周新来的同事分享Redis的原理和机制,想起2017年的时候测试环境Redis被攻击,最后只能重新安装服务器,今天试验一把利用Redis漏洞进行攻击…

成集云 | 英克对接零售O2O+线上商城 | 解决方案

方案介绍 零售O2O线上商城是一种新型的商业模式,它通过线上和线下的融合,提供更加便捷的购物体验。其中,O2O指的是线上与线下的结合,通过互联网平台与实体店面的结合,实现线上线下的互动和协同。线上商城则是指通过互…

AI优秀企业案例——机器人流程自动化:达观数据RPA

通过学习业内领先公司的最佳实践,我们可以更好地将它们应用到我们自己的公司和业务中。特别是第三部分,提供了大量应用案例,让我们一起期待看到这些案例的结尾。 1.简介 达观数据是一家专注于智能文本机器人的国家高新技术企业,…

Leetcode-面试题 02.02 返回倒数第 k 个节点

快慢指针:让快指针先移动n个节点,之后快慢指针一起依次向后移动一个结点,等到快指针移动到链表尾时,慢指针则移动到倒数第n个结点位置。 /*** Definition for singly-linked list.* public class ListNode {* int val;* …

矩阵等价和向量组等价的一些问题

什么是向量组?答:向量组是由若干同维数的列向量(或同维数的行向量)组成的集合。什么是向量组等价?答:两个向量组,各自拼成矩阵A和B,向量组等价就是三秩相等,即r&#xff…

点信息标注_BillboardTextActor3D

开发环境: Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example参考代码 demo解决问题:点附近创建左边或其他信息,且信息面板显示状态不受相机缩放、旋转影响 prj name: BillboardTextActor3D #include…

STM32笔记—定时器

目录 一、TIM简介 二、基本定时器(TIM6和TIM7) 1. TIM6和TIM7简介 2. TIM6和TIM7的主要特性 3. TIM6和TIM7的功能 3.1 时基单元 3.2 计数模式 3.3 时钟源 三、通用定时器 1. TIMx(2、3、4、5)简介 2. TIMx主要功能 3. 时钟选择 4. 影子寄存…

计算机视觉驾驶行为识别应用简述

一、什么是计算机视觉识别? 计算机视觉识别是一种基于图像处理和机器学习的人工智能应用技术,可以用于多个场景。常见应用场景包括人脸识别、场景识别、OCR识别以及商品识别等。今天以咱们国产系统豌豆云为例,为大家梳理一下在车辆驾驶行为中…

在Google Kubernetes集群创建分布式Jenkins(二)

上一篇博客在Google Kubernetes集群创建分布式Jenkins(一)-CSDN博客我介绍了如何在GCP的K8S集群上部署一个分布式的Jenkins,并实现了一个简单的Pipeline的运行。 在实际的开发中,我们通常都会按照以下的CICD流程来设置Pipeline 在我司的实际实践中&…

如何让群晖Audio Station公开共享的本地音频公网可访问?

文章目录 1. 本教程使用环境:2. 制作音频分享链接3. 制作永久固定音频分享链接: 之前文章我详细介绍了如何在公网环境下使用pc和移动端访问群晖Audio Station: 公网访问群晖audiostation听歌 - cpolar 极点云 群晖套件不仅能读写本地文件&a…