春节前,粗略研究了一下微信的公共帐号。用 Golang 实现了一个简单的 package wechat,用于接入微信公共帐号。当时就在思考,微信的文字交互过程如果要实现有一定逻辑的复杂过程,可能需要使用到状态机。然后,就看到了这篇文章:《State machines in Go (#golang)》。非常合时宜啊!翻译于此,以飨读者!
——–翻译分隔线——–
Go(#golang) 实现的状态机
我已经用 Go 代替 Python 重写了一个关键的服务组件。由于 Python 的解释器不是线程安全的,所以在解析的时候使用了全局锁。Go 与 Python 不同,它内建了并发支持,并且是静态编译的。
首先要实现一个状态机。Python 的版本是基于 David Mertz 的这篇文章。
Mertz 使用了面向对象的形式,定义了一个有着数据和方法的类。他的代码,抛开语法不谈,对于任何有着 C++、C# 和 Java 的面向对象经验的人来说都不会陌生。
不过 Go 没有提供在特定数据结构上内部关联方法的机制。作为代替,Go 允许联合方法到数据结构,这样任何方法都可以应用到任何结构上。(译注:class { methods } 和 struct { }; methods 的区别。)
这种形式与 Alan Kay 所表达的,关于最初的面向对象阶段比较接近。
先别忘了这些,下面是我最开始用 Go 结构体编写的状态机的类:
type Machine struct { Handlers map[string]func(interface{}) (string, interface{}) StartState string EndStates map[string]bool }
跟 Mertz 的定义一样,Handlers 是一个用 string 做键名的 map,map 项保存的值是函数,可以接收一个“物料”,并且返回下一个状态名的字符串和更新后的物料值。
Go 认为函数是一等公民对象,因此将它们在状态之间存储和传递跟在 Python 中的方式一样。
我仅仅在状态列表的最后做了一些改变:Mertz 使用一个字符串的列表,但由于没有办法在 Go 的列表中进行快速的定位,我使用了 map(在 Go 中,只能通过迭代遍历整个字符串列表,直到找到一个匹配项)。
由于处理函数的原型比较笨重,我为其建立了一个自定义函数类型:
type Handler func(interface{}) (string, interface{}) type Machine struct { Handlers map[string]Handler StartState string EndStates map[string]bool }
剩下的就是定义 Machine 结构体关联的方法。
首先定义的两个方法,一个提供了状态名关联到处理函数,另一个设定了结束状态:
func (machine *Machine) AddState(handlerName string, handlerFn Handler) { machine.Handlers[handlerName] = handlerFn } func (machine *Machine) AddEndState(endState string) { machine.EndStates[endState] = true }
值得说明的是由于 EndStates 是一个 map(在 Mertz 原始的版本中是 list),所以可以有多个终止处理过程的状态。
最后一个方法用于执行状态机,应用恰当的处理函数,并在到达结束状态时终止。
由于函数集合作为一等公民对象保存在 map 中,基于名字找到它们并且进行调用是很轻松的:
func (machine *Machine) Execute(cargo interface{}) { if handler, present := machine.Handlers[machine.StartState]; present { for { nextState, nextCargo := handler(cargo) _, finished := machine.EndStates[nextState] if finished { break } else { handler, present = machine.Handlers[nextState] cargo = nextCargo } } } }
唯一美中不足的是 Go 的强类型,在处理函数的原型中,需要指定物料的类型。
使用通用的 interface{} 作为类型,所有处理函数都需要对输入的物料进行类型断言,这样它们就可以处理任何数据(测试的例子使用了浮点作为物料,不过其实它可以是任何数据类型,甚至是自定义的结构体)。
完整的状态机已经作为 Go 包发布。