這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
春節前,粗略研究了一下的公用帳號。用 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 包發布。