duanji6997 2012-11-02 07:15
浏览 53

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

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.

  • 写回答

2条回答 默认 最新

  • duanliao3826 2012-11-02 08:36
    关注

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

报告相同问题?

悬赏问题

  • ¥15 微信小程序协议怎么写
  • ¥15 c语言怎么用printf(“\b \b”)与getch()实现黑框里写入与删除?
  • ¥20 怎么用dlib库的算法识别小麦病虫害
  • ¥15 华为ensp模拟器中S5700交换机在配置过程中老是反复重启
  • ¥15 java写代码遇到问题,求帮助
  • ¥15 uniapp uview http 如何实现统一的请求异常信息提示?
  • ¥15 有了解d3和topogram.js库的吗?有偿请教
  • ¥100 任意维数的K均值聚类
  • ¥15 stamps做sbas-insar,时序沉降图怎么画
  • ¥15 买了个传感器,根据商家发的代码和步骤使用但是代码报错了不会改,有没有人可以看看