状態遷移表からコード生成
それでは本番、状態(S)、事象(E)そして状態遷移表からStateパターンに基づいたC#コードを生成します。状態遷移表は「状態、事象、遷移先」の新たな状態の3つ組(例えば"disable状態でknockされたらenable状態に遷移"であれば { S.disabled, E.knock, S.enabled })をその組み合わせの数だけ列挙することとします。前回のアーティクルで例示した"ノック式ボールペン"であれば、以下のようなテンプレートになるでしょう。
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> <#@ include file="StatePattern.cstt" #> <#+ // NONEは状態を遷移しない(現状態を維持する)ことを示す特殊な遷移先 enum S { disabled, enabled, NONE }; // 状態を列挙 enum E { knock, write }; // 事象を列挙 // 以下に書かれた状態遷移表に基づいてコードを生成する Tuple<S,E,S>[] _transition_ = { // 状態, 事象, 次の状態 Tuple.Create(S.disabled, E.knock, S.enabled), Tuple.Create(S.disabled, E.write, S.NONE), Tuple.Create(S.enabled, E.knock, S.disabled), Tuple.Create(S.enabled, E.write, S.NONE), }; #>
前述のBody.tt.includeと同じ要領で、このテンプレートからC#コードに変換されるStatePattern.csttを用意すればいい。僕は最終的に得られる(生成させたい)C#コードを手書きし、動作を確認したのちプリントアウトしました。そして状態や事象など、差し替えの必要な部分/繰り返しで表現できる部分にマーカーで色を付け、それを基に<#~#><#=~#><#+~#>を埋め込んでいきました。
public abstract partial class State { <# foreach ( string s in Enum.GetNames(typeof(S)) ) { if ( s.ToUpper() == "NONE" ) { #> public static State <#= s #>() { return null; } <# } else { #> private static State <#= s #>_ = new <#= s #>(); public static State <#= s #>() { return <#= s #>_; } <# } } #> partial void _trace_(string st, string ev); public abstract string name(); protected abstract void do_enter(Context ctx); protected abstract void do_exit(Context ctx); public void start(Context ct) { do_enter(ct); } public void finish(Context ct) { do_exit(ct); } <# foreach ( string e in Enum.GetNames(typeof(E)) ) { #> protected virtual void do_action(<#= e #> ev, Context ct, ref State ns) {} protected virtual State transition(<#= e #> ev) { throw new System.NotImplementedException(name()+".<#= e #>"); } public State handle(<#= e #> ev, Context ct) { _trace_(name(),"<#= e #>"); State ns = transition(ev); do_action(ev, ct, ref ns); if ( ns == null ) { ns = this; } else { do_exit(ct); ns.do_enter(ct); } return ns; } <# } #> } <# foreach ( var s in Enum.GetNames(typeof(S)) ) { if ( s.ToUpper() == "NONE" ) continue; #> partial class <#= s #> : State { public override string name() { return "<#= s #>"; } partial void _enter_(Context ct); partial void _exit_(Context ct); protected override void do_enter(Context ctx) { _enter_(ctx); } protected override void do_exit(Context ctx) { _exit_(ctx); } <# foreach ( var t in _transition_ ) { if ( t.Item1.ToString() == s ) { #> protected override State transition(<#= t.Item2 #> ev) { return <#= (t.Item3.ToString().ToUpper() == "NONE") ? "null" : "State."+t.Item3.ToString()+"()" #>; } partial void _action_(<#= t.Item2 #> ev, Context ct, ref State ns); protected override void do_action(<#= t.Item2 #> ev, Context ct, ref State ns) { _action_(ev,ct,ref ns); } <# } } #> } <# } #>