ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

用Go实现一个状态机

2021-10-24 12:29:58  阅读:310  来源: 互联网

标签:return string 实现 状态机 retList int64 tStatus consts 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语言

标签:return,string,实现,状态机,retList,int64,tStatus,consts,Go
来源: https://blog.csdn.net/shida219/article/details/120932311

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有