はじめに
先日のastah* モデル駆動開発m2t(Model to Text) プラグイン のC#用テンプレートを作ってみたでは、クラスとインタフェースクラスからのコード出力を作成しました。
今回は、クラスの下に含まれるステートマシン図をもとに、状態遷移のコードを出力するように拡張してみましたので、内容を紹介します。
テンプレートのダウンロード
https://github.com/azuki8/m2tCsharpTemplate
ここから取得してください。
最初に前提としたい事柄
ステートマシン図はクラスに対応させて描く
ステートマシン図は、オブジェクト(≒クラス)の振る舞いを示すことができます。
ステートマシン図は、特定のクラスに対応させずに漠然とした振る舞いを描くこともできますが、それでは実装までつながりません。
よって、「特定のクラスに着目して、そのクラスの振る舞いをステートマシン図にすること」を前提に、テンプレートを作成します。
ステートマシン図での遷移発生時の処理の流れ
ステートマシン図でオブジェクトの振る舞いを表現するときの基本的な約束だけ説明します。
これ以外の説明は、ここでの範囲を超えるので省略します(ステートマシン図からのコード生成機能を拡張する段階で説明すると思います)。
下のような図は、ステートマシン図でよく見たことがあると思います。
この図を参考にしながら、
・基本的な用語の説明
・トリガー発生時に、どの順番で各アクティビティ/アクションが実行されるのか
だけをさらっと説明しますので、「この動きをコードとして出力するのだなぁ」と見ておいてください。
トリガ―[ガード条件]/遷移アクション
トリガ―:遷移が起こるきっかけ( 外部からの呼び出しやイベント、タイムイベントなど)
ガード条件:トリガーが発生したときに、この条件が真ならば、遷移する
遷移アクション:遷移時に実行される処理
処理の順序
状態1の時に、トリガーが発生しガード条件が真ならば、以下の順番で処理が行われます。
1. 状態1のexitアクション
2. 遷移アクション
3. 状態2のentryアクション
4. 状態2のdoアクティビティ
コード化
以上をコードで表現すると、次のように表現できます。
public void トリガー()
{
switch( _状態変数 ){ // 今の状態が、
case 状態1: // 状態1の時に、
if( ガード条件 == 真 ){ // ガード条件が真ならば、
状態1のexitアクション();
遷移アクション();
状態2のentryアクション();
状態2のdoアクティビティ();
}
break;
// 略
ステートマシン図の置き場
先ほど説明したように、ステートマシン図はクラスに対応させます。
ですので、astah*上でステートマシン図がどのクラスに対応するのかを分かるようにしなければなりません。
今回のテンプレートでは、クラスの下にステートマシン図を置くと、そのクラスのコード内に状態遷移のコードを出力するようにしました(下図参照)。
複数のステートマシン図を置いた場合は、複数の状態変数と状態遷移のコードを生成します。
出力されるコードの説明
状態変数部
/*** 状態変数 ***/
enum サンプル状態States { 状態1, 状態2 };
private サンプル状態States _サンプル状態;
状態の列挙体(enum)宣言と、状態変数の宣言を出力します。
状態変数の名前は、ステートマシン図の名前ではなく状態マシン名を使っていますので、状態マシン名をデフォルト名から直してください。
操作(メソッド)部
/*** 操作 ***/
public void トリガー()
{
switch( _サンプル状態 ){
case 状態1:
if( ガード条件 ){
exit状態1();
遷移アクション;
entry状態2();
do状態2();
}
break;
case 状態2:
break;
default:
throw new ArgumentOutOfRangeException("トリガー _サンプル状態");
break;
}
}
状態マシンを持ったクラスの場合は、操作内にswitch case文を出力します。
操作=トリガとして捉えます。
その操作が呼び出されたところで、
・switch文で現在状態の判断
・ガード条件のif文
・exit、entry、do などの遷移コード
が行われています。
もし、下図のように同一トリガーで複数ガード条件がある場合は、if() else if() 文を生成します。
/*** 操作 ***/
public void トリガー()
{
switch( _状態マシン1 ){
case 状態3:
if( x >= 5 ){
exit状態3();
action1();;
entry状態4();
do状態4();
}
else if( x < 5 ){
exit状態3();
action2();;
entry状態4();
do状態4();
}
break;
case 状態4:
break;
default:
throw new ArgumentOutOfRangeException("トリガー _状態マシン1");
break;
}
}
状態遷移のentryやexitの関数群
ステートマシン図内の各状態のentry/do/exitのコードを生成しています。
astah*で、入場動作/実行活動/退場動作内に書いたコードは、entry/do/exitの関数内にそれぞれ出力されます。
下記サンプルコードを見ると分かりますが、関数名を、entry + 状態マシン名 + 状態名 としています。
これは、複数のステートマシン図での同一状態名のときに関数名が衝突しないようにするためです(例:Idle状態の衝突)
/*** 状態遷移のコード ***/
/** サンプル状態の状態 **/
// 状態1状態のentry/do/exit
private void entryサンプル状態状態1()
{
_サンプル状態 = state.name;
状態1のentryアクション
}
private void doサンプル状態状態1()
{
状態1のdoアクティビティ
}
private void exitサンプル状態状態1()
{
状態1のexitアクション
}
// 状態2状態のentry/do/exit
private void entryサンプル状態状態2()
{
_サンプル状態 = state.name;
状態2のentryアクション
}
private void doサンプル状態状態2()
{
状態2のdoアクティビティ
}
private void exitサンプル状態状態2()
{
状態2のexitアクション
}
もう少しそれらしい例にすると、
が、次のコードになります。entry関数とexit関数内に出力されているのが分かります。
/** 状態マシン1の状態 **/
// 状態a状態のentry/do/exit
private void entry状態マシン1状態a()
{
_状態マシン1 = state.name;
notifyStarted();
}
private void do状態マシン1状態a()
{
}
private void exit状態マシン1状態a()
{
m_piyo.Stop();
}
さいごに
今回は、ステートマシン図の基本的なコード変換と、作成したテンプレートによる生成コードを説明しました。
しかし、これで終わりにしないぞ、、、。
というのは、ステートマシン図は振る舞いの図です。
ということは、基本的に自律的なものなのです。
自律的ということは、何かしらスレッド/タスクといった概念が出てきます。
せっかくC#のテンプレートを作っているので、C#のスレッドやタスクなどのコードもテンプレートから出力しても構わないはずです。
今回のテンプレートで生成されたコードに手作業で処理を追加してもいいのですが、
どうせなら、テンプレートで生成してみたいものです。
なので、引き続きテンプレートを拡張してみたいと思います。