状態遷移表からコード生成
それでは本番、状態(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); }
<# }
} #>
}
<# } #>
