Go语言状态机如何实现

免费教程   2024年05月10日 18:59  

这篇文章主要介绍“Go语言状态机如何实现”,在日常操作中,相信很多人在Go语言状态机如何实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言状态机如何实现”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一、状态机

1. 定义

有限状态机(Finite-state machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

2. 组成要素

现态(src state):事务当前所处的状态。

事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。

动作(action):事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。

次态(dst state):事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

状态流转(transition):事物从现态转为次态的整个过程。

3. 优点

代码抽象:将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元。这样相当于搭建乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。

简化流程:业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。

易扩展:在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。

业务建模:通过最小粒度的相邻状态拼接,最终组成了业务的整体graph。

二、代码

假设我们有要实现一个订单下单功能,上图是订单状态的流转图,方框为订单的状态,箭头旁的文字为事件。

1. database包packagedatabaseimport"fmt"//DB模拟数据库对象typeDBstruct{}//Transaction模拟事务func(db*DB)Transaction(funfunc()error)error{fmt.Println("事务执行开始。")err:=fun()fmt.Println("事务执行结束。")returnerr}//Order订单typeOrderstruct{IDint64//主键IDStateint//状态}typeOrderList[]*Order//查询所有订单funcListAllOrder()(OrderList,error){orderList:=OrderList{&Order{1,0},&Order{2,1},&Order{2,2},}returnorderList,nil}//UpdateOrderState更新订单状态funcUpdateOrderState(curOrder*Order,srcStateint,dstStateint)error{ifcurOrder.State==srcState{curOrder.State=dstState}fmt.Printf("更新id为%v的订单状态,从现态[%v]到次态[%v]\n",curOrder.ID,srcState,dstState)returnnil}

用来模拟数据库的操作,如数据库事务、查询所有订单、更新订单状态等。

2. fsm包packagefsmimport("fmt""reflect""zuzhiang/database")//FSMState状态机的状态类型typeFSMStateint//FSMEvent状态机的事件类型typeFSMEventstring//FSMTransitionMap状态机的状态转移图类型,现态和事件一旦确定,次态和动作就唯一确定typeFSMTransitionMapmap[FSMState]map[FSMEvent]FSMDstStateAndAction//FSMTransitionFunc状态机的状态转移函数类型typeFSMTransitionFuncfunc(paramsmap[string]interface{},srcStateFSMState,dstStateFSMState)error//FSMDstStateAndAction状态机的次态和动作typeFSMDstStateAndActionstruct{DstStateFSMState//次态ActionFSMAction//动作}//FSMAction状态机的动作typeFSMActioninterface{Before(bizParamsmap[string]interface{})error//状态转移前执行Execute(bizParamsmap[string]interface{},tx*database.DB)error//状态转移中执行After(bizParamsmap[string]interface{})error//状态转移后执行}//FSM状态机,元素均为不可导出typeFSMstruct{transitionMapFSMTransitionMap//状态转移图transitionFuncFSMTransitionFunc//状态转移函数}//CreateNewFSM创建一个新的状态机funcCreateNewFSM(transitionFuncFSMTransitionFunc)*FSM{return&FSM{transitionMap:make(FSMTransitionMap),transitionFunc:transitionFunc,}}//SetTransitionMap设置状态机的状态转移图func(fsm*FSM)SetTransitionMap(srcStateFSMState,eventFSMEvent,dstStateFSMState,actionFSMAction){ifint(srcState)<0||len(event)<=0||int(dstState)<0{panic("现态|事件|次态非法。")return}transitionMap:=fsm.transitionMapiftransitionMap==nil{transitionMap=make(FSMTransitionMap)}if_,ok:=transitionMap[srcState];!ok{transitionMap[srcState]=make(map[FSMEvent]FSMDstStateAndAction)}if_,ok:=transitionMap[srcState][event];!ok{dstStateAndAction:=FSMDstStateAndAction{DstState:dstState,Action:action,}transitionMap[srcState][event]=dstStateAndAction}else{fmt.Printf("现态[%v]+事件[%v]+次态[%v]已定义过,请勿重复定义。\n",srcState,event,dstState)return}fsm.transitionMap=transitionMap}//Push状态机的状态迁移func(fsm*FSM)Push(tx*database.DB,paramsmap[string]interface{},currentStateFSMState,eventFSMEvent)error{//根据现态和事件从状态转移图获取次态和动作transitionMap:=fsm.transitionMapevents,eventExist:=transitionMap[currentState]if!eventExist{returnfmt.Errorf("现态[%v]未配置迁移事件",currentState)}dstStateAndAction,ok:=events[event]if!ok{returnfmt.Errorf("现态[%v]+迁移事件[%v]未配置次态",currentState,event)}dstState:=dstStateAndAction.DstStateaction:=dstStateAndAction.Action//执行before方法ifaction!=nil{fsmActionName:=reflect.ValueOf(action).String()fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v],[%v].before\n",currentState,event,dstState,fsmActionName)iferr:=action.Before(params);err!=nil{returnfmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败,[%v].before,err:%v",currentState,event,dstState,fsmActionName,err)}}//事务执行execute方法和transitionFunciftx==nil{tx=new(database.DB)}transactionErr:=tx.Transaction(func()error{fsmActionName:=reflect.ValueOf(action).String()fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v],[%v].execute\n",currentState,event,dstState,fsmActionName)ifaction!=nil{iferr:=action.Execute(params,tx);err!=nil{returnfmt.Errorf("状态转移执行出错:%v",err)}}fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v],transitionFunc\n",currentState,event,dstState)iferr:=fsm.transitionFunc(params,currentState,dstState);err!=nil{returnfmt.Errorf("执行状态转移函数出错:%v",err)}returnnil})iftransactionErr!=nil{returntransactionErr}//执行after方法ifaction!=nil{fsmActionName:=reflect.ValueOf(action).String()fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v],[%v].after\n",currentState,event,dstState,fsmActionName)iferr:=action.After(params);err!=nil{returnfmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败,[%v].before,err:%v",currentState,event,dstState,fsmActionName,err)}}returnnil}

状态机包含的元素有两个:状态转移图和状态转移函数,为了防止包外直接调用,这两个元素都设为了不可导出的。状态转移图说明了状态机的状态流转情况,状态转移函数定义了在状态转移的过程中需要做的事情,在创建状态时指定,如更新数据库实体(order)的状态。

而状态转移图又包含现态、事件、次态和动作,一旦现态和事件确定,那么状态流转的唯一次态和动作就随之确定。

状态机的动作又包含三个:Before、Execute和After。Before操作在是事务前执行,由于此时没有翻状态,所以该步可能会被重复执行。Execute操作是和状态转移函数在同一事务中执行的,同时成功或同时失败。After操作是在事务后执行,因为在执行前状态已经翻转,所以最多会执行一次,在业务上允许执行失败或未执行。

状态机的主要方法有两个,SetTransitionMap方法用来设置状态机的状态转移图,Push方法用来根据现态和事件推动状态机进行状态翻转。Push方法中会先执行Before动作,再在同一事务中执行Execute动作和状态转移函数,最后执行After动作。在执行Push方法的时候会将params参数传递给状态转移函数。

3. order包

(1) order

packageorderimport("fmt""zuzhiang/database""zuzhiang/fsm")var(//状态StateOrderInit=fsm.FSMState(0)//初始状态StateOrderToBePaid=fsm.FSMState(1)//待支付StateOrderToBeDelivered=fsm.FSMState(2)//待发货StateOrderCancel=fsm.FSMState(3)//订单取消StateOrderToBeReceived=fsm.FSMState(4)//待收货StateOrderDone=fsm.FSMState(5)//订单完成//事件EventOrderPlace=fsm.FSMEvent("EventOrderPlace")//下单EventOrderPay=fsm.FSMEvent("EventOrderPay")//支付EventOrderPayTimeout=fsm.FSMEvent("EventOrderPayTimeout")//支付超时EventOrderDeliver=fsm.FSMEvent("EventOrderDeliver")//发货EventOrderReceive=fsm.FSMEvent("EventOrderReceive")//收货)varorderFSM*fsm.FSM//orderTransitionFunc订单状态转移函数funcorderTransitionFunc(paramsmap[string]interface{},srcStatefsm.FSMState,dstStatefsm.FSMState)error{//从params中解析order参数key,ok:=params["order"]if!ok{returnfmt.Errorf("params[\"order\"]不存在。")}curOrder:=key.(*database.Order)fmt.Printf("order.ID:%v,order.State:%v\n",curOrder.ID,curOrder.State)//订单状态转移iferr:=database.UpdateOrderState(curOrder,int(srcState),int(dstState));err!=nil{returnerr}returnnil}//Init状态机的状态转移图初始化funcInit(){orderFSM=fsm.CreateNewFSM(orderTransitionFunc)orderFSM.SetTransitionMap(StateOrderInit,EventOrderPlace,StateOrderToBePaid,PlaceAction{})//初始化+下单->待支付orderFSM.SetTransitionMap(StateOrderToBePaid,EventOrderPay,StateOrderToBeDelivered,PayAction{})//待支付+支付->待发货orderFSM.SetTransitionMap(StateOrderToBePaid,EventOrderPayTimeout,StateOrderCancel,nil)//待支付+支付超时->订单取消orderFSM.SetTransitionMap(StateOrderToBeDelivered,EventOrderDeliver,StateOrderToBeReceived,DeliverAction{})//待发货+发货->待收货orderFSM.SetTransitionMap(StateOrderToBeReceived,EventOrderReceive,StateOrderDone,ReceiveAction{})//待收货+收货->订单完成}//ExecOrderTask执行订单任务,推动状态转移funcExecOrderTask(paramsmap[string]interface{})error{//从params中解析order参数key,ok:=params["order"]if!ok{returnfmt.Errorf("params[\"order\"]不存在。")}curOrder:=key.(*database.Order)//初始化+下单->待支付ifcurOrder.State==int(StateOrderInit){iferr:=orderFSM.Push(nil,params,StateOrderInit,EventOrderPlace);err!=nil{returnerr}}//待支付+支付->待发货ifcurOrder.State==int(StateOrderToBePaid){iferr:=orderFSM.Push(nil,params,StateOrderToBePaid,EventOrderPay);err!=nil{returnerr}}//待支付+支付超时->订单取消ifcurOrder.State==int(StateOrderToBePaid){iferr:=orderFSM.Push(nil,params,StateOrderToBePaid,EventOrderPayTimeout);err!=nil{returnerr}}//待发货+发货->待收货ifcurOrder.State==int(StateOrderToBeDelivered){iferr:=orderFSM.Push(nil,params,StateOrderToBeDelivered,EventOrderDeliver);err!=nil{returnerr}}//待收货+收货->订单完成ifcurOrder.State==int(StateOrderToBeReceived){iferr:=orderFSM.Push(nil,params,StateOrderToBeReceived,EventOrderReceive);err!=nil{returnerr}}returnnil}

order包中做的事情主要有:

定义订单状态机的状态和事件;

创建一个状态机,并设置状态转移函数和状态转移图;

执行订单任务,推动状态转移。

(2) order_action_place

packageorderimport("fmt""zuzhiang/database")typePlaceActionstruct{}//Before事务前执行,业务上允许多次操作func(receiverPlaceAction)Before(bizParamsmap[string]interface{})error{fmt.Println("执行下单的Before方法。")returnnil}//Execute事务中执行,与状态转移在同一事务中func(receiverPlaceAction)Execute(bizParamsmap[string]interface{},tx*database.DB)error{fmt.Println("执行下单的Execute方法。")returnnil}//After事务后执行,业务上允许执行失败或未执行func(receiverPlaceAction)After(bizParamsmap[string]interface{})error{fmt.Println("执行下单的After方法。")returnnil}

(2) ~ (5)是订单不同动作的声明和实现。

(3) order_action_pay

packageorderimport("fmt""zuzhiang/database")typePayActionstruct{}//Before事务前执行,业务上允许多次操作func(receiverPayAction)Before(bizParamsmap[string]interface{})error{fmt.Println("执行支付的Before方法。")returnnil}//Execute事务中执行,与状态转移在同一事务中func(receiverPayAction)Execute(bizParamsmap[string]interface{},tx*database.DB)error{fmt.Println("执行支付的Execute方法。")returnnil}//After事务后执行,业务上允许执行失败或未执行func(receiverPayAction)After(bizParamsmap[string]interface{})error{fmt.Println("执行支付的After方法。")returnnil}

(4) order_action_deliver

packageorderimport("fmt""zuzhiang/database")typeDeliverActionstruct{}//Before事务前执行,业务上允许多次操作func(receiverDeliverAction)Before(bizParamsmap[string]interface{})error{fmt.Println("执行发货的Before方法。")returnnil}//Execute事务中执行,与状态转移在同一事务中func(receiverDeliverAction)Execute(bizParamsmap[string]interface{},tx*database.DB)error{fmt.Println("执行发货的Execute方法。")returnnil}//After事务后执行,业务上允许执行失败或未执行func(receiverDeliverAction)After(bizParamsmap[string]interface{})error{fmt.Println("执行发货的After方法。")returnnil}

(5) order_action_receive

packageorderimport("fmt""zuzhiang/database")typeReceiveActionstruct{}//Before事务前执行,业务上允许多次操作func(receiverReceiveAction)Before(bizParamsmap[string]interface{})error{fmt.Println("执行收货的Before方法。")returnnil}//Execute事务中执行,与状态转移在同一事务中func(receiverReceiveAction)Execute(bizParamsmap[string]interface{},tx*database.DB)error{fmt.Println("执行收货的Execute方法。")returnnil}//After事务后执行,业务上允许执行失败或未执行func(receiverReceiveAction)After(bizParamsmap[string]interface{})error{fmt.Println("执行收货的After方法。")returnnil}4. main包packagemainimport("fmt""zuzhiang/database""zuzhiang/order")funcmain(){order.Init()orderList,dbErr:=database.ListAllOrder()ifdbErr!=nil{return}for_,curOrder:=rangeorderList{params:=make(map[string]interface{})params["order"]=curOrderiferr:=order.ExecOrderTask(params);err!=nil{fmt.Printf("执行订单任务出错:%v\n",err)}fmt.Println("\n\n")}}

最后在main包里先初始化一个订单状态机,查询所有订单,并使用状态机执行订单任务,推动订单状态转移。注意多个订单可以用同一个状态机来进行状态的迁移。

到此,关于“Go语言状态机如何实现”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

域名注册
购买VPS主机

您或许对下面这些文章有兴趣:                    本月吐槽辛苦排行榜

看贴要回贴有N种理由!看帖不回贴的后果你懂得的!


评论内容 (*必填):
(Ctrl + Enter提交)   

部落快速搜索栏

各类专题梳理

网站导航栏

X
返回顶部