用Go实现一个状态机

工作中,很多同学会用到状态机,例如对一个工单进行创建、编辑、审核,在执行新动作前,要检查能否从当前状态流转到下一个状态。对这种需求,我们怎么实现呢?

数组

Go设计模式(22)-状态模式中说过,简单的状态管理使用数组即可完成,无需使用状态模式。以下图为例,状态之间的流转,无法跳跃,即只能从编辑中跳到提交,无法直接从编辑中跳到审核通过。

图片

这种实现方案很简单,配置数组用于检查:

func (s *shopWarehouse) statusCheck(currentStatus, targetStatus int64) bool {
	//target -> currents
	statusMap := map[int64][]int64{
		consts.Editing: []int64{consts.Editing, consts.Pass, consts.Reject},
		consts.Submit:  []int64{consts.Editing, consts.Reject},
		consts.Pass:    []int64{consts.Submit},
		consts.Reject:  []int64{consts.Submit},
		consts.Invalid: []int64{consts.Pass},
		consts.Delete:  []int64{consts.Reject, consts.Editing},
	}
	if statusList, ok := statusMap[targetStatus]; ok {
		if util.InArray(currentStatus, statusList) {
			return true
		}
		return false
	} else {
		return false
	}
}

数组在设置上,可以设置为target->current,也可设置为为current->target,current->target更易于理解。

状态检查需要两步,先查询当前状态,然后调用statusCheck函数,但两个操作之间,若有其它线程更改了当前状态呢?

其实可以用Go中加锁的例子进行解释,在Go锁,我终于搞懂了中,加互斥锁也分两步,获取当前状态,然后执行CAS,必然会遇到并发情况,对当前状态进行了变更。但没有影响,因为只要更新的那一刻,我们关注的数据状态没有变化,那就说明变更合法的,直接更新即可。

将状态放到数组里统一维护,比将各个状态分散开判断,在设计上要好得多。

上面的流转比较简单,而且没有跳跃情况。如果状态十分多,而且会跳跃,再用数组方案,维护成本极高。

此时,图的作用就发挥出来了。分支限界法回溯法里有很多广度优先遍历和深度优先遍历的例子,希望可以帮助大家回忆起相关算法。

这里先举一个状态流转例子:

图片

其中红色表示必须经过的节点,白色可以跳过,对于这种情况,我们应该怎样实现呢?

package main

import (
   "fmt"
   "reflect"
)

// 时序图
var StatusTimingGraph = map[string][]string{
   "A":  {"B1", "B2"},
   "B1": {"C1", "C2"},
   "B2": {"B1"},
   "C1": {"D"},
   "C2": {"C1"},
   "D":  {"E"},
}

// 核心节点
var CoreStatus = []string{
   "A",
   "B1",
   "C1",
   "E",
}
var StatusJumpGraph = InitJumpGraph(StatusTimingGraph, CoreStatus)

func InitJumpGraph(statusMap map[string][]string, coreStatus []string) map[string][]string {
   retMap := make(map[string][]string, 0)
   for status, statusList := range statusMap {
      retList := make([]string, 0)
      for _, tStatus := range statusList {
         retList = append(retList, tStatus)
         if InSlice(coreStatus, tStatus) {
            continue
         }
         tList := recursionGraph(tStatus, statusMap, coreStatus)
         for _, tStatus := range tList {
            if !InSlice(retList, tStatus) {
               retList = append(retList, tStatus)
            }
         }
      }

      retMap[status] = retList
   }
   return retMap
}
func recursionGraph(status string, statusMap map[string][]string, coreStatus []string) []string {
   retList := make([]string, 0)
   if statusList, ok := statusMap[status]; ok {
      for _, tStatus := range statusList {
         retList = append(retList, tStatus)
         if InSlice(coreStatus, tStatus) {
            continue
         }
         retList = append(retList, recursionGraph(tStatus, statusMap, coreStatus)...)
      }
   }
   return retList
}
func InSlice(a, b interface{}) bool {
   exist, _ := InSliceWithError(a, b)
   return exist
}
func InSliceWithError(a, b interface{}) (exist bool, err error) {

   va := reflect.ValueOf(a)

   if va.Kind() != reflect.Slice {
      err = fmt.Errorf("parameter a must be a slice")
      return
   }

   if reflect.TypeOf(a).String()[2:] != reflect.TypeOf(b).String() {
      err = fmt.Errorf("type of parameter b not match with parameter a")
      return
   }

   for i := 0; i < va.Len(); i++ {
      if va.Index(i).Interface() == b {
         exist = true
         return
      }
   }

   return
}

func main() {
   originStatus := "A"
   targetStatus := "B1"
   statusList, ok := StatusJumpGraph[originStatus]
   fmt.Println(StatusJumpGraph)
   if !ok {
      fmt.Println("状态有误")
      return
   }

   if !InSlice(statusList, targetStatus) {
      fmt.Println("状态不合规,无法流转")
      return
   }
}

输出:

➜ myproject go run main.go

map[A:[B1 B2] B1:[C1 C2] B2:[B1] C1:[D E] C2:[C1] D:[E]]

这个代码其实是深度优先遍历,CoreStatus意味深度优先遍历终止的位置。使用该方案,无论状态图多复杂,只需修改状态配置即可。

但这个方案有一个问题,正好可以留给大家思考:如果是幂等情况,需要如何实现?即D->D的情况。

资料

  1. UML:https://www.processon.com/view/link/6174cb1b63768912b562ce29

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

图片

往期文章回顾:

  1. 设计模式

  2. 招聘

  3. 思考

  4. 存储

  5. 算法系列

  6. 读书笔记

  7. 小工具

  8. 架构

  9. 网络

  10. Go语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员麻辣烫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值