トピック

説明 ページの先頭へ

状態マシンはモデル要素の動的な振る舞いをモデル化するために使用します。具体的には、システムの振る舞いがイベント駆動される様相をモデル化します (「概念: イベントとシグナル」を参照)。状態マシンは状態に依存する振る舞い、つまりモデル要素が置かれている状態に応じて変化する振る舞いを定義するために、特に使用します。状態に応じて振る舞いが変わることのないモデル要素については、その振る舞いを状態マシンで記述する必要はありません (これらの要素は通常、データ管理が主な責務である受動的なクラスです)。特に、状態マシンは、その操作を (クラスの状態マシンの遷移として) 実装するためにコール イベントとシグナル イベントを使用するアクティブなクラスの振る舞いをモデル化するために使用されます。

状態マシンは遷移によってつながるいくつかの状態から構成されます。状態とは、オブジェクトがなんらかの作業を遂行中であるか、またはイベントを待っている状況です。遷移とは、なんらかのイベントによって引き起こされる、2 つの状態の関係を表します。つまり、イベントによってなんらかのアクションまたは評価が行われて、特定の終了状態がもたらされることです。状態マシンに関係する諸要素を図 1 に示します。

図 1. 状態マシンの表記法

簡単なエディタを例に取り上げましょう。このエディタは、「」、「コマンド待ち」、「テキスト待ち」という状態を取る有限状態マシンと考えられます。この状態マシンの状態を遷移させるトリガとなるイベントには、「ファイルをロード」、「テキストを挿入」、「文字を挿入」、「保存して終了」があります。エディタの状態マシンは、以下の図 2 に示されています。

図 2. 簡単なエディタの状態マシン

状態 ページの先頭へ

状態とは、オブジェクトがなんらかの作業を遂行中であるか、またはイベントを待っている状況です。オブジェクトは有限の時間にわたってある状態を取ることができます。状態には下に示すプロパティがあります。

名前 ある状態を別の状態から区別する文字列。状態は、名前のないことを意味する無名であってもかまいません。
入状時 / 退状時アクション 状態の開始と終了時に行われるアクション
内部遷移 状態の変化を伴わずに進行する遷移
サブ状態 状態のネスト構造で、個別の (逐次的にアクティブな) サブ状態と並列的な (並列的にアクティブな) サブ状態とがある
遅延イベント 該当の状態においては処理されず、その処理が後に回されて、ほかの状態にあるオブジェクトによって処理されるように待ち行列に入れられた、イベントのリスト

図 1 に示したように、1 つのオブジェクトの状態マシンに定義できる特別な状態が 2 つあります。初期状態は、状態マシンまたはサブ状態のデフォルトの開始点を示します。初期状態は塗り潰された黒丸で表されます。最終状態は状態マシンでのアクションの実行が完了したこと、またはそれに包まれている状態が完了したことを示します。最終状態は内側が黒く塗りつぶされた二重丸で表されます。初期状態も最終状態も実際には擬似状態です。どちらも、名前を除いて、通常は正規の状態にはない部分を有する可能性があります。初期状態から最終状態への遷移には、関連する諸機能がすべて含まれる可能性があります。その中には、ガード条件やアクションが含まれますが、トリガ イベントは含まれません。

遷移 ページの先頭へ

遷移は 2 つの状態の関係を表します。すなわち、最初の状態にあるオブジェクトがなんらかのアクションを行った後で、指定されたイベントが発生するか指定された条件が満たされたときに、2 番目の状態に移ることを示します。そのような状態の変化に際して、遷移は「発火する」と言います。遷移が発火するまでは、オブジェクトは「ソース」状態にあると言います。遷移が発火した後では、オブジェクトは「ターゲット」状態にあると言います。遷移には下に示すプロパティがあります。

ソース状態 遷移の影響を受ける状態。オブジェクトがソース状態にある場合、遷移のトリガとなるイベントをオブジェクトが受け取り、ガード条件がある場合にはそれが満たされたときに、進行中の遷移は発火することが可能となる
イベント トリガ ソース状態にあるオブジェクトが受け取ったときに、遷移を発火可能にする (ガード条件が満たされたものとして) イベント
ガード条件 イベント トリガが受け取られ遷移が引き起こされたときに評価される論理式。評価の結果が真であれば、遷移は発火可能となり、評価の結果が偽であれば遷移は発火不可能。同じイベントによって引き起こされる遷移がほかにない場合、イベントは失われる。
アクション 状態遷移で表されるオブジェクトに直接作用し、そのオブジェクトに関連するほかのオブジェクトに間接的に作用する、実行可能な不可分の処理
ターゲット状態 遷移が完了した後で現れる状態

遷移には複数のソースがあることがあります。その場合、複数の並列する状態を結合して遷移は進みます。また、遷移のターゲットが複数の場合もあります。その場合、遷移の結果は複数の並列する状態に分かれます。

イベント トリガ

状態マシンという状況においては、イベントは状態遷移を引き起こす可能性のある刺激の発生です。イベントの例として、シグナル イベント、コール イベント、時間の経過、状態の変化などが挙げられます。シグナルやコールにはパラメータがあり、そのパラメータの値が遷移に使用されます。パラメータの例には、ガード条件やアクションの式があります。トリガがなくても遷移の発生は可能です。これは、イベント トリガなしの遷移として表されます。そのような遷移は、ソース状態のアクションが完了したときに暗黙的に引き起こされます。これは完了遷移とも呼ばれます。

ガード条件

遷移のトリガとなるイベントが発生した後でガード条件が評価されます。同じソース状態から同じイベント トリガによって複数の遷移を引き起こすことも可能です。ただし、その場合は、ガード条件は別々である必要があります。1 つのガード条件は遷移に関して 1 回だけ、イベントが発生したときに評価されます。論理式からオブジェクトの状態を参照できます。

アクション

アクションは実行可能な不可分の処理です。つまり、アクションはイベントによって割り込まれることがなく、最後まで遂行されます。これはほかのイベントによって割り込まれる可能性のある作業とは対照的です。アクションには、処理の呼び出し (状態マシンで表されるオブジェクト、それと関連するオブジェクトへの)、ほかのオブジェクトの作成と破棄、ほかのオブジェクトへのシグナルの送信などがあります。シグナルを送信する場合、シグナル名の前に send というキーワードが接頭辞として付加されます。

入状時アクションと退状時アクション

入状時アクションと退状時アクションを利用すると、該当の状態が始まる、または終わるたびに、それぞれ同じアクションが発動できます。そうすると、始まり、終わるたびに明示的にアクションを指定する必要がなくなります。入状時アクションと退状時アクションには引数またはガード条件を持たせることはできません。モデル要素のための状態マシンのトップレベルにある入状時アクションに、その要素が作成されたときに状態マシンが受け取る引数を表すパラメータを持たせることができます。

内部遷移

内部遷移を利用すると、状態の内部だけでイベントを処理できます。したがって、入状時アクションと退状時アクションを起動することが避けられます。内部遷移にはパラメータを伴うイベントとガード条件を持っていることがあり、基本的に、割り込みハンドラを表します。

遅延イベント

遅延イベントとは、その処理の実施が保留されて、後でイベントが保留されない状態になったときに遅れて処理されるものです。その状態に達すると、そのイベントの発生が起動されて、それが新規に発生した場合のように遷移を引き起こします。遅延イベントを実装するためには、内部的なイベント キューを必要とします。遅延イベントが発生した場合には、キューに入れられます。オブジェクトがイベントを遅延させない状態に入るとすぐに、それらのイベントはキューから取り出されます。

サブ状態 ページの先頭へ

簡単な状態にはサブ構造がありません。サブ状態 (ネストされた状態) のある状態を複合状態と呼びます。サブ状態は任意のレベルへネストされます。ネストされた状態マシンには初期状態と最終状態が 1 つずつあります。サブ状態は複雑でフラットな状態マシンを簡素化して見せるために使用します。つまり、複雑な状態のうちの一部はある特定の状況 (包含状態) においてのみ発生し得ることを示します。

ar_state2.gif (12801 bytes)

図 3. サブ状態

包含する複合状態の外部のソースから開始された遷移のターゲットの状態は複合状態であることもサブ状態の場合もあります。複合状態がターゲットとされる場合、ネストされた状態マシンには初期状態が含まれている必要があります。複合状態の遷移が開始されてその入状時アクションが (もしあれば) 起動された後で、初期状態に制御が移されます。ネストされた状態がターゲットとされる場合、複合状態の入状時アクションが (もしあれば) 起動され、それからネストされた状態の入状時アクションが (もしあれば) 起動された後で、ネストされた状態に制御が移されます。

複合状態から発生する遷移はそのソースとして複合状態またはサブ状態を取ることがあります。どちらの場合でも、まずネストされている状態の方からほかに制御が移され (退状時アクションがある場合はそれも起動され)、次に複合状態からほかに制御が移され (退状時アクションがある場合はそれも起動され) ます。ソースが複合状態である遷移は基本的にネストされている状態マシンの作業に割り込みをかけることになります。

履歴状態 ページの先頭へ

遷移が複合状態に入ると、ネストされた状態マシンのアクションは初期状態から再び開始されます (遷移がサブ状態を直接ターゲットとしていない場合)。ただし、別の指定がなされている場合を除きます。履歴状態を利用すると、複合状態から抜け出る直前のサブ状態に、状態マシンを再び入れられます。履歴状態の使用例を図 4 に示します。

ar_state3.gif (8052 bytes)

図 4. 履歴状態

一般的なモデリング技法 ページの先頭へ

状態マシンは、オブジェクトのライフタイムにわたって、その振る舞いをモデル化するために最もよく使用されます。オブジェクトの振る舞いが状態に依存するときに、状態マシンは特に必要です。状態マシンを持つ可能性のあるオブジェクトには、クラス、サブシステム、ユース ケース、インターフェイス (インターフェイスを実現するオブジェクトによって満たされる必要がある状態を宣言するため) があります。

すべてのオブジェクトが状態マシンを必要とするとは限りません。オブジェクトの振る舞いが単純で、データを格納または取り出す程度のものである場合、オブジェクトの振る舞いは状態に関係がないため、その状態マシンにはほとんど意味がありません。

オブジェクトのライフタイムをモデリングするには、3 つの事柄が関係します。オブジェクトが応答する対象のイベントの指定、それらのイベントに対する応答、そして現在の振る舞いに対する過去の影響です。オブジェクトのライフタイムをモデリングすることには、イベントに対してオブジェクトが意味のある応答をするための順序を決めることも関係します。それは、オブジェクトが作成された時点からモデルが破棄されるまでに至ります。

オブジェクトのライフタイムをモデリングする手順は下記のとおりです。

  • 状態マシンの状況がクラス、ユース ケース、またはシステム全体のどれであるかを設定します
    • 状況がクラスまたはユース ケースである場合、隣接するクラスを収集する。その中には、親クラスまたは関連または依存関係によってアクセス可能なクラスを含みます。それらの隣接するクラスはアクションのターゲットの候補となり、ガード条件に含めるターゲットの候補ともなります。
    • 状況がシステム全体である場合、システムの 1 つの振る舞いに焦点を絞り、それに関連するオブジェクトのライフタイムを検討します。システム全体のライフタイムは、意味のあるように焦点を絞るには大きすぎます。
  • オブジェクトの初期状態と最終状態を確定します。初期状態と最終状態に事前条件または事後条件がある場合は、それらも定義します。
  • オブジェクトが応答するイベントを決定します。これらのイベントはオブジェクトのインターフェイスまたはプロトコルにあります。
  • 初期状態から開始して最終状態に至るまで、オブジェクトが取る可能性のあるトップ レベルの状態を列挙します。その後、適切なイベントによって引き起こされる遷移で、それらの状態を繋ぎます。これらの遷移の追加を続けます。
  • 入状時アクションまたは退状時アクションがあれば、それを識別します
  • サブ状態を使用して、状態マシンを拡張または簡素化します
  • 状態マシン内の遷移を起動するすべてのイベントが、そのオブジェクトによって実現されるインターフェイスによって期待されるイベントと合致することをチェックします。同様に、そのオブジェクトのインターフェイスによって期待されるすべてのイベントがその状態マシンによって処理されることをチェックします。最後に、イベントを明示的に無視したい (たとえば、遅延イベント) 箇所を探します。
  • 状態マシン内のすべてのアクションが包含するオブジェクトの関係、メソッド、操作によってサポートされることをチェックします
  • 期待されるイベントのシーケンスとその対応とを比較しながら、状態マシンをトレースします。到達できない状態と状態マシンがスタックしている状態を見つけ出します。
  • 状態マシンを再調整または再構成した場合、元々の機能が変更されていないことを確認します

ヒント ページの先頭へ

  • できるだけ、遷移を詳細にコードで記述するのではなく、状態マシンの働きを視覚的に表現します。たとえば、いくつかのシグナルに基づいて 1 つの遷移を起動し、それから詳細なコードを使用して、シグナルの違いに応じて制御の流れを変えるように管理することは避けます。その代わりに、別々のシグナルによって、別々の遷移が起動されるようにします。追加の振る舞いを隠ぺいする遷移を記述するコード内で条件つきのロジックを使用することを避けます。
  • 何を待っている状態か、または何が行われている状態かに基づいて、状態に名前を付けます。状態とは「時点」ではなく、状態マシンが何かの発生を待っている期間であることに注意してください。たとえば、end よりも waitingForEnd の方が、timeout よりも timingSomeActivity の方がよい名前です。状態がアクションと誤解されるような名前を付けないようにします。
  • 状態マシン内のすべての状態と遷移に一意の名前を付けます。それによって、ソースのレベルでのデバッグが容易になります。
  • 状態変数 (振る舞いの制御に使用される属性) は慎重に使用し、新しい状態を作成する代わりとして使用しません。状態がごくわずかしかなく、状態に依存する振る舞いがほとんどまたはまったく存在せず、状態マシンが含まれるオブジェクトと同時にまたは独立して発生する振る舞いがほとんどまたはまったく存在しない場合には、状態変数を使用してもよいでしょう。潜在的に同時に発生する可能性のある複雑で状態に依存する振る舞いがある場合、または処理すべきイベントが状態マシンを含むオブジェクトの外で発生する可能性がある場合、複数のアクティブなオブジェクト (おそらくコンポジションとして定義された) のコラボレーションを使用することを検討します。
  • 単一のダイアグラムに 5 ± 2 を超える状態がある場合、サブ状態を使用することを検討します。次の常識が当てはまります。10 の状態が 1 つの完全に規則正しいパターンになっているのはかまいませんが、2 つの状態の間に 40 の遷移がある場合は再考する必要があります。状態マシンが理解しやすいものであることを確認します。
  • 何がイベントを起動するか、遷移の際に何が起こるか、という点に着目して遷移に名前を付けます。分かりやすい名前を選択します。
  • 選択点に来た場合、その選択事項を操作の対象であることを示す明白な一連のシグナルとして (たとえば、msg->data > x 上で選択する代わりに) オブジェクトに提示し、送信者などの中間アクターに意思決定をさせ、その決定事項の内容がシグナル名からわかるようにして (たとえば、値に名前を付けてメッセージ データをチェックする代わりに、isFull や isEmpty といった名前を付けたシグナルを使用する) シグナルを送信させるようにするなど、その選択の責任をほかのコンポーネントに委譲できるかどうかを検討します。
  • 選択点で答えられる質問に、内容を表すような名前を付けます。たとえば、isThereStillLife または isItTimeToComplain などです。
  • 任意のオブジェクト内で、選択点の名前を一意に保ちます (遷移名を一意に保つのと同じ理由によります)
  • 遷移に関して過度に長いコード部分がありますか。代わりに関数を使用して、共通のコード部分を関数として把握すべきですか。遷移は高水準の擬似コードのように記述し、C++ の関数と同等以上の厳格な長さの規則に従うべきです。たとえば、コードの行数が 25 を超える遷移は長すぎると考えられます。
  • 関数にはその機能を表す名前を付けます
  • 入状時アクションと退状時アクションに特に注意を払います。変更を行ったのに、入状時アクションと退状時アクションにその変更を反映するのを忘れることがよくあります。
  • 安全機能として退状時アクションを使用できます。たとえば、heaterOn 状態の退状時アクションで暖房のスイッチを切ります。ここで、アクションは宣言を励行するために使用されます。
  • 一般に、サブ状態には 2 つ以上の状態があります。ただし、状態マシンが抽象的な場合は例外とします。包含する要素のサブクラスによって、サブ状態を改良します。
  • アクションまたは遷移内で条件つきロジックの代わりに、選択点を使用します。選択点は目に見えるのに対して、条件つきロジックはコード内に隠蔽されていて見過ごしやすいからです。
  • ガード条件を避けま。
    • イベントによって複数の遷移が起動される場合、どのガード条件が最初に評価されるのかを制御できません。そのため、結果も予測不可能です。
    • 複数のガード条件が「真」になり得ますが、従うことができるのは 1 つの遷移のみです。選択されたパスは予想できません。
    • ガード条件は視覚的ではなく、その存在を「目で見る」ことは困難です
  • 状態マシンがフロー チャートと似たものにならないようにします
    • フロー チャートに似ている状態マシンは、次のような実際には存在しない抽象的なものをモデル化しようとする試みを示している可能性があります
      • アクティブ クラスを使用して、受動的 (またはデータ) クラスに最も適した振る舞いをモデル化します
      • 非常に緊密に結合されたデータ クラスとアクティブ クラスを使用して、データ クラスをモデル化します (つまり、データ クラスは型情報を伝えるために使用されていますが、アクティブ クラスはデータ クラスに関連づけられるべきデータの大部分を保持しています)。
    • 状態マシンを誤用していると、次のような兆候が現れます
      • 主としてコードを再利用するためだけに、「それ自体」に対してメッセージが送信されます
      • 状態の数はわずかでも、選択点が多くあります
      • 場合により、状態マシンにサイクルがありません。そのような状態マシンは、プロセス制御アプリケーションまたはイベントのシーケンスを制御しようとしているときは有効です。分析の間にそのような状態マシンが現れることは、通常は状態マシンが劣化してフロー チャートになっていることを示します。
    • 問題が把握されたら、次のように対応します
      • アクティブ クラスを小単位に分割して、責任を明確化することを検討します。
      • 問題アクティブ クラスに関連づけられているデータ クラスに振る舞いをもっと多く移動します
      • アクティブ クラス関数に振る舞いをもっと多く移動します
      • データに頼る代わりに、もっと意味のあるシグナルを作成します

抽象状態マシンを使用した設計ページの先頭へ

抽象状態マシンとは、詳細をさらに追加してからでないと、実用的な目的に使用できない状態マシンです。一般的で再利用可能な振る舞いを定義するために使用できます。この振る舞いは後続のモデル要素でさらに精緻化します。

図 5. 抽象状態マシン

図 5 の抽象状態マシンを検討してみましょう。この図で示される単純な状態マシンは、イベント駆動システム内の多種多様な要素の振る舞い (自動「制御」) を最も抽象的なレベルで表したものです。それらの要素はすべてこの高水準のフォームを共有するにもかかわらず、さまざまな要素タイプはその目的に応じて実際に稼働するときには大幅に異なった、詳細な振る舞いを取る可能性があります。したがって、この状態マシンは、さまざまな特殊化したアクティブ クラスのルート クラスとして働く、なんらかの抽象クラスとして定義される可能性が最も高いでしょう。

そこで、継承を使用して、この抽象状態マシンを 2 とおりの違った方法で改良してみましょう。その 2 つの改良方法を R1 と R2 として、図 6 に示します。親クラスから継承した要素は、職別できるようにするために灰色で描いてあります。

図 6. 図 5 の状態マシンの 2 とおりの改良

この 2 つの改良結果には明白な違いがあります。それは、稼働状態をどのように分解するかという点と、元の「開始」遷移を拡張する方法に現れています。当然のことながら、どちらを選択するかは、改良の内容が明らかになった後でのみ決定できます。つまり、抽象クラスにおける 1 つの全面的な遷移だけではこの選択は行えません。

連鎖状態 ページの先頭へ

上記のタイプの改良では、遷移の始まりと遷移の終わりの両方を「継続」できる能力が基本的に重要です。入状点と最終状態を連続的な遷移で結合すれば、その意味を表すのに十分であると思われるかもしれません。残念ながら、拡張が必要な複数の異なる遷移がある場合は、これだけでは十分ではありません。

抽象的な振る舞いのパターンに必要なことは、単一の RTC 列の範囲内ですべて実行される 2 つ以上の遷移セグメントを連続的につなぐ方法です。このことは、階層的な状態に入っていく遷移は前の部分と後の部分の 2 つに分けられることを意味します。前の部分は状態の境界のところで終了し、後の拡張部分は新しい状態の中に入っていきます。同様に、階層的にネストされた状態から外へ去り行く遷移も前の部分と後の部分の 2 つに分けられます。前の部分は包含されている状態の境界で終了し、後の部分は境界からターゲットの状態の中に入っていきます。この効果は UML において連鎖状態の概念を導入することによって達成されます。これは UML の状態概念のステレオタイプである (≪chainState≫) によってモデル化されます。これは、唯一の目的が自動的な (トリガなしの) 遷移をさらに入力遷移へと「連鎖」させる状態です。連鎖状態には内部構造がありません。つまり、入状時アクションも、内部的な作業も、退状時アクションもありません。また、連鎖状態にはイベントによって起動される遷移もありません。連鎖状態は任意の数の入力状態を取ることができます。連鎖状態はトリガイベントのない、1 つの去り行く遷移を持つことが可能です。この遷移は、入力遷移によって状態が有効になったときに、自動的に発火します。この状態の目的は入力遷移を別の出力遷移につなげることです。入力遷移と連鎖される出力遷移との間で、前者は包含する状態の内側の別の状態につながり、後者は包含する状態の外側の別の状態につながります。連鎖状態を導入する目的は、包含する状態の内部仕様を外部の環境から隔離すること、すなわち、カプセル化することです。

実際、連鎖状態は、ある遷移を特定の連続的な遷移へとつなげる働きをする、「通路」状態を表します。連続的な遷移が定義されていない場合、遷移は連鎖状態に入って止まります。そして、包含する状態の中のなんらかの遷移が処理を続行させるために発火する必要が出てきます。

図 7 の状態マシンのセグメントの例は連鎖状態とその表記法を示しています。状態マシン図において、連鎖状態は適切な階層状態の中に位置している小さな白丸によって表されています (この表記法は連鎖状態と類似した初期状態と最終状態に似ています)。丸は連鎖状態のステレオタイプを表すアイコンであり、通常は便宜的に境界の近くに描かれます (実際、カプセル上のポートの表記法に似て、包含する状態の境界上にそれらのアイ コンを描く、表記法の変形もあります)。

図 7. 連鎖状態と連鎖された遷移

この例における連鎖された遷移は 3 つの連鎖された遷移のセグメント、e1/a11-/a12-/a13 で構成されています。シグナル e1 が受け取られると、e1/a11 というラベルが付けられた遷移が開始され、a11 のアクションが実行されて、連鎖状態の c1 に達します。その後、c1 と c2 との間の連続的な遷移が開始されて、最後に c2 も連鎖状態なので、c2 から S21 への遷移が開始されます。このパスに沿った状態のすべてに退状時アクションと入状時アクションがあるとすると、実際にアクションが進行する順序は次のようになります。

  • S11 の退状時アクション
  • アクション a11
  • S1 の退状時アクション
  • アクション a12
  • S2 の入状時アクション
  • アクション a13
  • S21 の入状時アクション

このすべてが単一の RTC 列の範囲内ですべて実行されます。

これと対照的に、直接的な遷移 e2/a2 のアクションの実行順序は次のようになります。

  • S11 の退状時アクション
  • S1 の退状時アクション
  • アクション a2
  • 状態 S2 の入状時アクション
  • 状態 S21 の入状時アクション


Rational Unified Process   2003.06.15