私は最初のGoプログラムであるSMTPサーバーを作成していますが、FSMを使用してネットワークプロトコルの状態遷移を表現するのは賢明だと思いました。私はこのhaskellSMTPFSMの例が本当に好きだったので、その後大まかにモデル化しました。
コンストラクター引数として遷移テーブルを取り、イベントを受け取り、状態テーブルで一致する適切なハンドラー関数を呼び出すRunメソッドを持つ単純なFSMタイプを作成しました。次に、接続からの着信SMTPコマンドを処理した後にFSMを利用する必要がある「セッション」タイプがあります。
FSMトランジションは次のようになります。
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内で定義する必要があります。この遷移表をすべてのセッションに対して1回だけ定義し、必要なセッションハンドラー関数に適切にアクセスできる方法を考えることはできません。
このプログラムを単純な手続き型で作成した場合、これらの問題は発生しません。FSMは、すべてのハンドラー関数に直接アクセスできます。
私が考えることができる唯一のことは、関数ポインターを返さないようにFSMを変更することですが、代わりに、セッションが適切な関数にマップする任意の定数を返します。
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を再初期化する必要があるという問題が修正されますが、少しハックな感じもします。この問題を回避する方法について誰かが何か提案がありますか?これは、問題を示す不完全なSession.goへのリンクです。