我正在编写我的第一个Go程序,一个SMTP服务器,我认为使用FSM来表示网络协议的状态转换会很优雅。我真的很喜欢this haskell SMTP FSM example所以我在那之后对它进行了松散的建模。
我created a simple FSM type将转换表作为构造函数参数,并且有一个Run方法接受一个事件,并调用它在状态表中匹配的相应处理函数。然后我有一个“会话”类型,这是在处理来自其连接的传入SMTP命令后需要利用FSM。
这是FSM Transition的样子:
type Transition struct {
from State
event Event
to State
handler func() string
}
然后,在我的Session对象中,我被迫在其构造函数中定义转换表,以便我可以访问其转换操作的方法:
func (s *SmtpSession) NewSession() {
transitions := []Transition{
{Initial, Rset, Initial, sayOk},
{HaveHelo, Rset, HaveHelo, sayOk},
{AnyState, Rset, HaveHelo, resetState},
...
{Initial, Data, Initial, needHeloFirst},
{HaveHelo, Data, HaveHelo, needMailFromFirst},
{HaveMailFrom, Data, HaveMailFrom, needRcptToFirst},
{HaveRcptTo, Data, HaveData, startData},
}
smtpFsm = StateMachine.NewMachine(transitions)
}
当所有会话具有基本相同的FSM时,必须在每个会话中创建此FSM的实例,这似乎很浪费。我宁愿只有一些“静态”FSM,可以给它一个转换表,然后Run方法将采用当前状态和一个事件并返回结果“动作”函数。
然而,这是我遇到麻烦的地方。因为所有处理函数实际上都是Session对象的方法,所以我必须在Session中定义它。我想不出一种方法,我只能为所有会话定义一个转换表,并且仍然可以访问我需要的Session处理函数。
如果我以直接的程序风格编写这个程序,那么我就不会遇到任何这些问题。 FSM可以直接访问所有处理函数。
我唯一能想到的是改变我的FSM不返回函数指针,而是返回一些任意常量,然后Session将映射到相应的函数:
var transitions = []Transition{
{Initial, Rset, Initial, "sayOk"},
{HaveHelo, Rset, HaveHelo, "sayOk"},
{AnyState, Rset, HaveHelo, "resetState"},
...
}
var smtpFsm = NewStateMachine(transitions)
func (s *Session) handleInput(cmd string) {
event := findEvent(cmd)
handler := findHandler(smtpFsm.Run(s.currentState, event))
handler(cmd)
}
func (s *Session) findHandler(handlerKey string) {
switch handlerKey {
case "sayOk":
return sayOk
case "resetState":
return resetState
}
}
这将解决必须为每个会话重新初始化新FSM的问题,但它也感觉有点hackish。有没有人对我如何避免这个问题有任何建议?这是展示问题的link to the incomplete Session.go。
答案 0 :(得分:3)
如果您没有处理程序是方法,那么整个事情会更容易方式,而是将实例作为参数的函数。这大致相同,但您可以使用type State func(Session) State
类型的设置,并且可以更轻松地考虑它。
答案 1 :(得分:2)
我不确定这个概念是多么的暗示。随着关注点的分离,那么你的状态机只是发出一个令牌并且仍然不知道将来如何使用它。在handleInput
方法中,您将使用该令牌执行操作,在这种情况下,是在会话对象上找到适当的方法。
但是整个事情似乎终结于我。您有一个想要了解Session
的转换表。你有一些我猜的包fsm
很少,但也想知道Session
,因为它依赖于转换表来使包有用。
我会说要么切断链接并发出const
Session
findHandler
可以用来找到合适的方法,走更多的程序路线,或者将这些位合并得更近(可能涉及放弃转换表) )。
或者,如果您真的想要使用效率低下的路线,让Action("sayOk")
按名称反映相应的方法,并删除该开关语句,但仅为了好玩:)
您也可以考虑实施的一些替代方案。标准库中有很多很好的例子。查看文件/模板的包文件:
http://golang.org/src/pkg/text/template/parse/lex.go
http://golang.org/src/pkg/text/template/parse/parse.go
我所说的要点是,正如您已经注意到的那样,如果您希望在函数之外定义转换表,则需要引用令牌或函数,而不是您没有的实例的方法。但是为了好玩,使用下面的内容,你可以在转换表中说Session
,然后将package main
import (
"fmt"
"reflect"
)
type Foo struct{}
func (f *Foo) Bar() string {
return "hello"
}
func Action(name string) func(f *Foo) string {
return func(f *Foo) string {
s := reflect.ValueOf(f).MethodByName(name).Call([]reflect.Value{})
return s[0].String()
}
}
func main() {
f := &Foo{}
a := Action("Bar")
fmt.Println(a(f))
}
传递给交付的结果函数。
{{1}}