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

I'm writing my first Go program, an SMTP server, and I thought that using a FSM to represent the state transitions of the network protocol would be elegant. I really liked this haskell SMTP FSM example so I loosely modeled it after that.

I've created a simple FSM type which takes a transition table as its constructor argument and has a Run method which takes an event and will call the appropriate handler function it matches in the state table. I then have a "Session" type which is what needs to utilize the FSM after processing incoming SMTP commands from its connection.

Here is what a FSM Transition looks like:

type Transition struct {
    from    State
    event   Event
    to      State
    handler func() string
}

Then, in my Session object, I'm forced to define the transition table in its constructor so I can have access to its methods for the transition actions:

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)
}

This seems wasteful to have to create an instance of this FSM as part of each session, when all sessions will have essentially the same FSM. I'd much rather just have some sort of "static" FSM which can be given a transition table, and then the Run method will take a current state, and an event and return the resulting "action" function.

However, that is where I run into trouble. Because all of the handler functions are actually methods of the Session object, I must define it within Session. I can't think of a way I can define this transition table only once for all sessions and still have the appropriate access to the Session handler functions I need.

If I wrote this program in a straight procedural style then I wouldn't have any of these problems. The FSM would have access to all the handler functions directly.

The only thing I can think of is altering my FSM to not return function pointers, but instead return some arbitrary constant that the Session will then map to the appropriate function:

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
    }
}

This will fix the problem of having to re-intialize a new FSM for each session but it also feels a little hackish. Does anyone have any suggestions for how I might avoid this problem? Here is a link to the incomplete Session.go that demonstrates the problem.

I'm not sure how hackish the general idea is. With the separation of concerns, then you're state machine is simply emitting a token and remains unaware of how that will be used later on. Within your handleInput method, you'd be using that token to perform an action, which in this case is to find the appropriate method on your session object.

But the whole thing seems at ends to me. You have a transition table that wants to be aware of Session. You have package fsm that I'm guessing does very little but also wants to be aware of Session due to its dependence on the transition table for the package to be of any use.

I would say either cut the link and emit a const that Session can use to find an appropriate method, go the more procedural route, or merge the bits closer (likely involving ditching the transition table).

Or if you really wanted to go the hackishly inefficient route, let findHandler reflect the appropriate method by name and cut out that switch statement, but only for fun :)

You also might consider some alternatives for implementation. There are a lot of great examples in the standard library. Check out the package files for text/template:

http://golang.org/src/pkg/text/template/parse/lex.go

http://golang.org/src/pkg/text/template/parse/parse.go

The gist of what I'm saying is, and as you've already noted, if you want that transition table defined outside of a function as you have it, you'll need to reference a token or a function, not a method of an instance you don't have. But for fun, using something like below, then you could say Action("sayOk") in your transition table, and pass in Session to the resulting function delivered.

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))
}

This whole thing gets way easier if you don't have your handlers be methods, and instead functions that take the instance as a parameter. This is roughly the same thing, but you could have a type State func(Session) State type of setup and have a much easier time thinking about it.