LoginSignup
27
30

More than 5 years have passed since last update.

[状態マシン図] 状態マシン図を実装する

Last updated at Posted at 2014-04-30

Qiita2番目の投稿です。今日初めてMarkdown書きました。Markdown練習中です。


状態マシン図の設計モデルができたら、今度はそれを実装に変換しよう。

状態マシン図を実装する方法はいろいろあり、それぞれ一長一短がある。ここではもっとも簡単な形の実装例をひとつあげてみよう。

状態マシンは、「今の状態と発生したイベントから、次の状態を決定する」関数のモデル、と考えることができる。今回紹介する実装例は、

今の状態と 発生したイベントを引数にとり、次の状態を返すイベント処理関数、

次の状態 = HandleEvent(今の状態, 発生したイベント);

という関数を呼びだして、状態を更新しつづけるものである。組込みシステムでは、RTOSから1つイベントを受信してはこの関数を呼び出すイベントループを構成する。以下のような実装になる。

状態 s = 最初の状態;
while(true) {
  イベント e = ReceiveEvent();
  s = HandleEvent(s, e);
}

これで状態マシンを実装することができる。具体的な状態マシン図と実装を見てみよう。実装に使う言語はC言語にしよう。

code102.png

typedef enum State { S1,S2 } State;
typedef enum Event { E1,E2 } Event;

void main(void)
{
  State s = S1;
  while(true) {
    Event e = ReceiveEvent();
    s = HandleEvent(s,e);
  }
}

State HandleEvent(State s,Event e)
{
  switch(s) {
    case S1:
      if (e == E2) {
        s = S2;
      }
      break;
    case S2:
      if ((e == E1) && g1()) {
        a1();
        s = S1;
      }
      break;
    default:
      ASSERT(false);
      break;
  }
  return s;
}

イベント処理関数 HandleEventは、引数eに、E1でもE2でもないイベントが渡されたら、それを無視して、今の状態と同じ状態を返す。

また、S2を起点とする遷移にはガード条件g1()が設定されている。S2の状態にいるときに、イベントE1がおきたとき、g1()が成立したならば、S1に遷移することを示している。実装を確認してほしい。

各遷移にアクションを設定し、各状態に入場時アクション、退場時アクションを設定してみよう。

code152.png

typedef enum State { Undefined,S1,S2 } State;
typedef enum Event { E1,E2 } Event;

void main(void)
{
  State s = Undefined;
  a0();
  s = S1;
  entry1();
  while(true) {
    Event e = ReceiveEvent();
    s = HandleEvent(s,e);
  }
}

State HandleEvent(State s,Event e)
{
  switch(s) {
    case S1:
      if (e == E2) {
        exit1();
        a2();
        s = S2;
        entry2();
      }
      break;
    case S2:
      if ((e == E1) && g1()) {
        exit2();
        a1();
        s = S1;
        entry1();
      }
      break;
    default:
      ASSERT(false);
      break;
  }
  return s;
}

StateにUndefinedという値を追加した。a0()を実行する間は、
状態はS1でもS2でもない、Undefinedの状態である。

entry1()とexit1()を実行する間は、状態はS1であり、
entry2()とexit2()を実行する間は、状態はS2となるように、
アクションと状態を更新する順序を工夫している。

Undefinedの状態は、履歴疑似状態の実現でも必要となるので、
状態マシンの実装はいつも、Undefinedの状態から始まるように
しておけばよい。

このように、

次の状態 = HandleEvent(今の状態, 発生したイベント);

という関数呼出しを繰り返せば、状態マシンは実装することができる。

コンポジット状態ではイベントの処理の関数を入れ子にしてゆけばよい。直交状態では、領域ごとにイベントの処理の関数を作り、それぞれの領域ごとに状態を覚えてゆけばよい。履歴疑似状態では、その領域または、その領域以下のすべての状態を覚えておくための変数を別に用意し、履歴が設定されている状態を退場するときに、その変数に覚えておいて、次回入場するときにそれを戻せばよい。

より複雑な状態マシン図も実装してみてほしい。

また、状態マシン図の別の実装としては、GoF23パターンの1つ、Stateパターンが有名である。


参考記事

  1. [状態マシン図] 遷移 と イベント[ガード]/アクション
    http://saltheads.blog134.fc2.com/blog-entry-141.html
  2. [状態マシン図] UML2.0 状態機械 モデル
    http://saltheads.blog134.fc2.com/blog-entry-138.html
  3. [Haskell] Haskellで 状態マシンを書く
    http://saltheads.blog134.fc2.com/blog-entry-80.html
27
30
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
30