如何从对象的构造函数中提取变量初始化?

时间:2012-11-02 07:15:04

标签: go state-machine

我正在编写我的第一个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

2 个答案:

答案 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}}
相关问题