菜鸟笔记
提升您的技术认知

状态模式 golang实现-ag真人游戏

一 什么是有限状态机 

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

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

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

网站地图