Skip to content

Latest commit

 

History

History
947 lines (670 loc) · 50.9 KB

tut_uml_modeling_bs09.adoc

File metadata and controls

947 lines (670 loc) · 50.9 KB

スコアの構造を図にする

ここからは、スコアを計算するのに使う要素と、要素同士の関連、ゲームを進める動作をモデル図を使って整理してみましょう。

ゲームスコアのオブジェクト図を描く

吟味したユースケース記述を参照しながら、実際のゲームの様子を具体的な要素を使って図に表してみることで、スコアの計算にはどんな要素があればよいか考えてみましょう。

具体的な要素とは、ボウリングでいえば、ゲームやその各フレーム、ピンの数、スコアやボーナスを指します。 このようなことを図で表すには「オブジェクト図(インスタンス図と呼ぶこともあります)」が向いています。

プロジェクトに設計モデルを追加する

まず、プロジェクトに設計モデルを追加しましょう。 ここで言う設計モデルは、設計用に作成するモデル図を格納する場所だと思ってください。

Tip

ここでいう設計とは、対象とする業務やサービスに必要となる構成要素を洗い出すことと、それらを使った振舞いを検討することだと考えておけばよいでしょう。

プロジェクトに設計モデルを追加する
  1. プロジェクトにモデルを追加する。

    • 構造ツリー上で、プロジェクトを選択する。

    • 右クリックしてポップアップメニューを開き、「モデルの追加>モデル」を選択する( プロジェクトにモデルを追加した )。

{half-width}
Figure 1. プロジェクトにモデルを追加した
  1. 追加したモデルに名前をつける

{half-width}
Figure 2. モデルに設計用のモデルとして名前をつけた

設計モデルにオブジェクト図を追加する

ゲームスコアのオブジェクトが作られていく様子をオブジェクト図で表してみましょう。 {astah} で「オブジェクト図」を作成するときは「クラス図」を使います。

ゲームスコアのオブジェクト図を追加する
  1. モデルにクラス図を追加する( モデルにクラス図を追加する )。

{three-quarters-width}
Figure 3. モデルにクラス図を追加する
  1. 追加した図を「ゲームスコアのオブジェクト図」とする( 「ゲームスコアのオブジェクト図」を追加する )。

{half-width}
Figure 4. 「ゲームスコアのオブジェクト図」を追加する

ゲームのオブジェクトを作成する

ユースケース記述を読むと、「ゲーム」と「フレーム」のオブジェクトが必要そうです。 まず、ゲームを表すオブジェクトを追加しましょう。

ゲームを表すオブジェクトを追加する
  1. パレットから「インスタンス仕様」を選択して、図に配置する( 「インスタンス仕様」を追加する )。

{three-quarters-width}
Figure 5. 「インスタンス仕様」を追加する
  1. 名前が「インスタンス仕様0」(数字は作るたびにつけられる)となっている。これを、具体的なゲームを表す名前に変える。ここでは「game1」とする( 名前を「game1」に変更する )。

    • これはこのオブジェクト図で表そうとしているゲームにつけた識別子。

    • 名前の文字列を直接クリックして編集してもよいし、オブジェクトを選択した状態でプロパティーから編集してもよい。

{three-quarters-width}
Figure 6. 名前を「game1」に変更する
  1. このオブジェクトを、ゲームを表す「Game」クラスのオブジェクトとするために「Gmae」を追加する。

{three-quarters-width}
Figure 7. オブジェクトを選択してクラスを追加する
{half-width}
Figure 8. クラス「GameScore」を定義する
  1. オブジェクトの表示も「game1 : GameScore」のように変わる( クラスを追加した結果「game1」が「game1:GameScore」に変わった )。

{quarter-width}
Figure 9. クラスを追加した結果「game1」が「game1:GameScore」に変わった

フレームのオブジェクトを作成する

ゲームと同じように、フレームについてもインスタンス仕様を追加して、クラス名として「Frame」をつけます。 また、ユースケース記述から、1投目と2投目のピン数、スペアボーナスとストライクボーナスを保持する必要がありそうだとわかっています。 これらは、個別のインスタンス仕様と考えることもできますが、ここでは Frame に従属する情報と考えて属性にしてみます。

フレームを表すオブジェクトを追加し、「Frame」クラスと属性を定義する
  1. パレットから「インスタンス仕様」を図に追加する。

  2. フレーム名として「01」、クラス名として「Frame」をつける( フレームオブジェクトと「Frame」クラスを追加する )。

{three-quarters-width}
Figure 10. フレームオブジェクトと「Frame」クラスを追加する
  1. フレームオブジェクトを選択した状態で、プロパティーボタンをクリックする( 「Frame」クラスのプロパティーダイアログを開く )。

{three-quarters-width}
Figure 11. 「Frame」クラスのプロパティーダイアログを開く
  1. 「Frame」クラスに従属する情報をクラスの属性に追加する( 「Frame」クラスに従属する情報を属性に追加した )。

    • 「属性」タブを開く。

    • 「+」ボタンで属性を追加し、名前を入力する。

    • 「first」「second」「spare_bonus」「strike_bonus」を定義する。

{half-width}
Figure 12. 「Frame」クラスに従属する情報を属性に追加した
  1. 追加したオブジェクトは 属性が追加された後の「Frame」クラスのオブジェクト のように変わる。

    • 属性は追加されたが、このオブジェクトには属性値はまだ設定されていない。

{quarter-width}
Figure 13. 属性が追加された後の「Frame」クラスのオブジェクト
  1. 作成したフレームオブジェクトを選択すると、属性のインスタンス(スロットと呼ぶ)を編集するプロパティーが表示される( オブジェクトの属性値のプロパティーを開く )。

    • 各属性の「値」の欄をダブルクリックすると、値が設定できるようになる。

  2. オブジェクトに属性値を設定する。

    • 1ストライクなので投目(first)を 10 にする。

    • 2投目(second)は投球していないが、ここでは 0 としておく。

    • ボーナスは未確定だが、これも 0 としておく。

{three-quarters-width}
Figure 14. オブジェクトの属性値のプロパティーを開く
{quarter-width}
Figure 15. オブジェクトに属性値が設定された

もちろん、別の捉え方をしているのであれば、オブジェクトや保持する属性などが異なってきます。 この演習では、 オブジェクトに属性値が設定された のように捉えて、以降の設計を進めます。

ゲームの進行に合わせてオブジェクトを追加する

続いて、進行中のゲームのスコア( 第6フレーム投球後のスコアの例 )を例として、オブジェクト図にインスタンスを追加してみます。

{full-width}
Figure 16. 第6フレーム投球後のスコアの例

各フレームの情報を格納するデータ領域は、1ゲーム分を先に用意しておく方法や、ゲームの進行に応じて1フレームずつ増やす方法もあります。 ここでは、1フレームずつ追加する方法を使ってみましょう。 また、前後のフレームとのつながりを保持する方法にも、配列やリストを使う方法があります。ここでは、連結リスト(Linked List)を使ってみることにしましょう。

そして、ゲームからみて「先頭のフレーム」と「現在投球中のフレーム」がわかるよう、「game1」 オブジェクトからは、それぞれ別のリンクを張って、辿れるようにしておきます。

ではまず、ここまでに作成した「GameScore」クラスのオブジェクト「game1」と「Frame」クラスの1フレーム目のオブジェクト「01」の間にリンクを引くところから始めましょう。

オブジェクトの属性値を設定する
  1. ゲームからフレームへリンク(関連のインスタンス)を引く。

  1. ゲームはフレームを参照するので、誘導可能の矢印をつける。逆は未確定なので誘導可能性を未確定(矢印をつけない)にしておく。

  1. ゲームがフレームを参照するときに使うリンク端に名前をつける。ここでは先頭のフレームを指すリンクなので、リンク端名「head」をつける。

  1. 同様にして、現在のフレームを指すリンクを追加し、リンク端名「current」をつける( 別のリンク「current」を追加した )。

第2フレームを追加しましょう。 追加するフレームのピン数は、 第6フレーム投球後のスコアの例 を参照してください。

第2フレームを追加し、ピン数を設定する
  1. パレットから「インスタンス仕様」を図に追加する。

  2. 追加したインスタンス仕様の名前を「02」を入力すると、図に反映される( インスタンス仕様を追加して名前とクラスを設定した )。

  3. ベースクラスのプルダウンメニューから「Frame」を選択する。

{three-quarters-width}
Figure 24. インスタンス仕様を追加して名前とクラスを設定した
  1. Frameクラスに設定後は、プロパティーにピン数などのスロットが表示される。

  2. 第6フレーム投球後のスコアの例 を参照して、1投目、2投目のピン数を設定する( 追加したフレームにピン数を設定した

{three-quarters-width}
Figure 25. 追加したフレームにピン数を設定した

前後のフレームとのつながりには連結リスト(Linked List)を使ってみることにしました。 連結リストになるよう、追加した第2フレームと第1フレームの間にリンク(関連のインスタンス)を追加しましょう。

フレームの間にリンクを追加する
  1. パレットからリンクを選択し、「01:Frame」から「02:Frame」へリンクを引き、誘導可能にする( フレームの間にリンクを追加し、誘導可能にした )。

{half-width}
Figure 26. フレームの間にリンクを追加し、誘導可能にした
  1. 追加したリンクを選択した状態でプロパティーを編集する( 第2フレーム側のリンク端の名前を「next」とした )。

    • ターゲットが「02」になっているリンク端のタブを選択する。

    • リンク端の名前を「next」とする。

{three-quarters-width}
Figure 27. 第2フレーム側のリンク端の名前を「next」とした
  1. 同様にして、previousを意味するリンク「prev」も追加する( 第1フレームへ向かうリンクを追加して、リンク端の名前を「prev」とした )。

{half-width}
Figure 28. 第1フレームへ向かうリンクを追加して、リンク端の名前を「prev」とした
  1. 第1フレームの「prev」、第2フレームの「next」のつながる先はないので、それぞれについてインスタンス仕様から「nil」を追加しリンクを追加しておく(両端に「nil」へのリンクを追加した )。

{three-quarters-width}
Figure 29. 両端に「nil」へのリンクを追加した
  1. 現在投球中のフレームを指すリンク「current」は、第2フレームを指すように変更する( 「current」が第2フレームを指すように変更した )。

    • リンクを選択し、誘導可能なリンク端(「current」と書いてある側)をマウスでドラッグする。

    • マウスカーソルを第2フレームの枠内へ移動してドロップすると、接続先を変更できる。

{three-quarters-width}
Figure 30. 「current」が第2フレームを指すように変更した
  1. 第1フレームはストライクだったので、第2フレームの2投分によってストライクボーナスを計算してスロットの値に反映する( 第1フレームのストライクボーナスを計算して、スロット値を更新した )。

    • スペアボーナスとストライクボーナスに設定する値は、ユースケース記述「 [use_case_desc08] 」に記述した方法を使って求める。

{three-quarters-width}
Figure 31. 第1フレームのストライクボーナスを計算して、スロット値を更新した

これで、第2フレームまで投球した場合のオブジェクト図ができました。 同じようにして、 第6フレーム投球後のスコアの例 を参照しながら、第6フレーム投球後のオブジェクト図を作成してみましょう( 第6フレーム投球後のオブジェクト図 )。

{full-width}
Figure 32. 第6フレーム投球後のオブジェクト図

オブジェクト図を作成してみることで、スコアを記録するために必要なクラスや、クラスが持つ属性などを検討できましたね。

また、ストライクやスペアのボーナスには「次のフレーム」あるいは「次の次フレーム」のピン数が影響します。 ユースケースに従ってオブジェクト図にオブジェクトを追加することで、現在のフレームの1投目あるいは2投目にピン数を入力されたときが、「前のフレーム」や「前の前のフレーム」のボーナスを計算するタイミングということもわかってきました。

Note

構成要素のどの部分をインスタンス仕様とし、どの部分をインスタンス仕様の属性として扱うかによって、作成されるオブジェクト図は変わってきます。 第6フレーム投球後のオブジェクト図 は、何通りも作成しうるオブジェクト図の作成例の1つです。

より実務的な検討においては、複数の捉え方を考えた上でその考えに基くオブジェクト図を作成し、それらの中からより妥当と考えられるものを選ぶべきでしょう。

ゲームスコアのクラス図を描く

オブジェクト図が作成できたので、これを元にクラス図を作成してみましょう。

設計モデルにクラス図を追加する

まず、「ゲームスコアのクラス図」を追加しましょう。

ゲームスコアのクラス図を追加する
  1. モデルにクラス図を追加する( モデルにクラス図を追加する )。

{half-width}
Figure 33. モデルにクラス図を追加する
  1. 追加した図を「ゲームスコアのクラス図」とする( 追加した図を「ゲームスコアのクラス図」とした )。

{half-width}
Figure 34. 追加した図を「ゲームスコアのクラス図」とした

ゲームのクラスを追加する

「構造ツリー」をみると、オブジェクト図を作成したとき登録した「 GameScore 」クラスや「 Frame 」クラスが見つかります。 これらを1つずつドラッグ&ドロップして、クラス図に追加します( GameScore クラスと Frameクラスをクラス図に追加する )。

{three-quarters-width}
Figure 35. GameScore クラスと Frameクラスをクラス図に追加する

クラス間の関連を追加する(1)

前に作成したオブジェクト図( 第6フレーム投球後のオブジェクト図 )を参照して、記載されているリンクを関連として追加します。 まず、「 GameScore 」クラスから「 Frame 」クラスへ向かって連を引きましょう。

クラス間の関連を追加する
  1. パレットから、矢印付きの関連を選択し、「 GameScore 」クラスから「 Frame 」クラスへ向かって関連を引く( 先頭フレームを指す関連「head」を引いた )。

{half-width}
Figure 36. 先頭フレームを指す関連「head」を引いた
  1. 関連を選択した状態で、プロパティーからターゲットが Frame の関連端のタブを開き、名前を「head」とする( 関連端名を「head」とした )。

{three-quarters-width}
Figure 37. 関連端名を「head」とした
  1. 「 GameScore 」クラスからみた「Frame」クラスへの多重度を設定する。

    • まだ1投もしていないときはフレームがなく、1投して以降はフレームが複数ある。すなわち、先頭のフレームを指すリンク(関連のインスタンス)は、「つながっていない」か「1つある」かのいずれかになる。

    • この事情に見合うよう、 関連を選択した状態で、プロパティーからターゲットが Frame の関連端のタブを開き、「多重度」を「 0..1 」に設定する( 関連の多重度を「 0..1 」に設定した )。 .

{three-quarters-width}
Figure 38. 関連の多重度を「 0..1 」に設定した
  1. 「head」と同様、「current」についても関連を引く( 現在のフレームを指す関連「current」を引き、関連端名と多重度を設定した )。

    • パレットから、矢印付きの関連を選択し、「 GameScore 」クラスから「 Frame 」クラスへ向かって関連を引き、関連端名をつける。

    • 「 GameScore 」クラスからみると、まだ1投もしていないときはフレームがなく、1投して以降はフレームが複数ある。つまり、現在のフレームを指す関連のインスタンス(リンク)は、「ない」か「1つある」かのいずれかになる。この事情に見合うよう、関連の多重度を設定する。

{quarter-width}
Figure 39. 現在のフレームを指す関連「current」を引き、関連端名と多重度を設定した

クラス間の関連を追加する(2)

次に、フレーム同士の間に引かれていた「next」「prev」というリンクを関連として引きましょう。 もとのリンクは「 Frame 」クラスのインスタンスから、同じ「 Frame 」クラスのインスタンスへと引かれていました。 このようなリンクを関連に反映するときは、自クラスへつながる関連(自分から自分へ向かう関連)として表します。 また、先頭と末尾のフレームは、「nil」とつながっていました。 このようなリンクを反映するときは、つながっていない関連があるとみなします。つながっていない場合があるときは、関連の多重度に「 0 」の場合を含めます。結果として、この部分の多重度は「 0..1 」となります。

フレームからフレームへの関連を引く
  1. 「Frame 」クラスから「 Frame 」クラスへ向かって関連を引く。

{quarter-width}
Figure 40. マウスをクラスの上でクリックし、クラスの外でクリックし、再びクラスの上でクリックする
{quarter-width}
Figure 41. 自分自身へ向かう関連が引けたら、関連の線を調整する
  1. 関連端名と多重度を設定する

{half-width}
Figure 42. 関連端名と多重度を設定した
  1. 「next」についても関連端名と多重度を設定する( 「next」についての関連を追加した)。

    • 自分自身へ向かう関連を追加する。

    • 関連端名を「next」、多重度を「 0..1 」とした。

{half-width}
Figure 43. 「next」についての関連を追加した
Tip

関連「next」と「prev」は、ひとつの双方向の関連として描くこともできます。このときは、ひとつの双方向関連の両端の関連端名を「next」と「prev」とします( 関連「next」と「prev」をひとつの双方向関連で表した場合 )。

{quarter-width}
Figure 44. 関連「next」と「prev」をひとつの双方向関連で表した場合

クラスに操作を追加する

「GameScore」クラスは、ゲームの進行とともに「Frame」クラスのオブジェクトを作成し、スコアを記録していきます。 このとき、外部からゲームの進行に応じてピン数を受け取る必要がありますね。 そこで、ゲームを進めつつ、ピン数を「GameScore」クラスへ渡す「Game」クラスを追加しましょう( Gameクラスを追加した )。

{quarter-width}
Figure 45. Gameクラスを追加した

また、「Game」クラスには、スコアラーの入力を受けつけてゲームを進めるための働きが必要になります。 クラスにそのような働きを持たせるときは「操作」を用意します。

ここは、ゲームを進める操作ということで名前を「play」としておきましょう。 操作の内容は、振舞いのモデルで検討しましょう。 ここでは操作が必要ということまで決めておけばよいでしょう。

「Game」クラスに操作「play」を追加する( Gameクラスに操作「play」を追加した
  1. 「Game」クラスを選択した状態で、プロパティーから「操作」タブを選択する。

  2. 左下の「+」アイコンをクリックして、操作のエントリを追加する。

  3. 操作の名前を編集して「play」にする。

  4. クラスの表示も操作「play」が追加される。

{three-quarters-width}
Figure 46. Gameクラスに操作「play」を追加した

また、「GameScore」クラスにも、ゲームの進行とともにピン数を受け取ってスコアを記録する働きが必要になりますね。 そこで、「scoring」という操作を用意します( GameScoreクラスに操作「scoring」を追加した )。 この操作の処理内容も、振舞いのモデルで検討しましょう。 「Game」クラスから関連を引きます。関連端名は「game_score」としました。ゲームがあればスコアは必ず用意するので、多重度は「 1 」に設定しています。

{three-quarters-width}
Figure 47. GameScoreクラスに操作「scoring」を追加した

これでクラス図の最初の版はできました。 まだ振舞いを検討していませんので、追加した操作は吟味できていません。 振舞いを検討するときに、これらの操作についても検討しましょう。

スコア記録の振る舞いを図にする

スコアの構造を検討できたので、こんどは、ゲームを進めるときの動作をモデル図を使って整理してみましょう。

振る舞いの図を選択する

振る舞いを表す図には「 よく使う振舞いのモデル図 」に挙げたようなものがあります。

Table 1. よく使う振舞いのモデル図
図の種類 説明

シーケンス図

クラスやオブジェクトの間のやり取りを整理するのに向いています。

アクティビティ図

処理の流れや条件による処理の分岐を表すのに向いています。

ステートマシン図

起きるのを待っているできごと(イベント)と、イベントが起きた時の処理(アクション)を使って振る舞いを表すのに向いています。

では、ゲームを進めるときの動作を考えるとき、どの図を使うのがよいでしょうか。 たとえば、「Game」クラスと「GameScore」クラスは「 GameクラスとGameScoreクラスの間のやり取り 」に示すような手順となります。

GameクラスとGameScoreクラスの間のやり取り
  1. システムの利用者は「Game」クラスの操作「play」を呼び出す。

  2. 実際のゲームが進行する間、「Game」クラスは次のことを繰り返す。

    1. 「Game」クラスは、ゲームで投球がある都度、ピン数の入力を受け付ける。

    2. 「Game」クラスは、「GameScore」クラスの操作「scoring」を呼び出して、入力されたピン数を渡す。

    3. 「GameScore」クラスは、操作「scoring」を実行してスコアを記録する。

このやり取りを図で表したいなら、シーケンス図が向いているでしょう。 ですが、この程度であるなら、必ずしも図を描いて処理の内容を検討するほどではないでしょう。

一方で、「GameScore」クラスの操作「scoring」の動作では、ゲームが進むたびにフレームのインスタンスの追加やボーナスの計算が必要です。 また、ボーナスを計算するには、いまどのような状況か(何フレーム目の何投目か)を判断する必要もあります。 ということは、操作「scoring」の動作では、ゲームの状況を判断するために「状態を維持する」必要がありそうです。 そして、ピン数を受け取ったときにスコアを計算するというのは、「できごとが起きたときに何かをする」という考え方で動作を整理する必要があることを意味しています。 この考え方に従って振る舞いを検討するには「ステートマシン図」を使うのがよさそうです。

ステートマシン図を描く

では、「GameScore」クラスの操作「scoring」の動作を検討するために、ステートマシン図を作成しましょう。

振る舞いの捉え方によって、振る舞いの図がどのようなものになるのかはかわります。 この演習では「ピンの入力を待っていてピン数が入力されたらスコアを計算する」という考え方に従って振る舞いを考えてみましょう。

設計モデルにステートマシン図を追加する

まず、「ゲームスコアのステートマシン図」を追加しましょう。 追加したいのは「GameScore」クラスの振舞いを表すステートマシン図なので、それがわかるよう「GameScore」クラスに対して追加しましょう。

ゲームスコアのステートマシン図を追加する
  1. 「GameScore」クラスにステートマシン図を追加する( 「GameScore」クラスにステートマシン図を追加する )。

{half-width}
Figure 48. 「GameScore」クラスにステートマシン図を追加する
  1. 追加した図を「GmaeScoreのscoringのステートマシン図」とする( 追加した図を「GameScoreのscoringのステートマシン図」とした )。

{three-quarters-width}
Figure 49. 追加した図を「GameScoreのscoringのステートマシン図」とした

状態と状態遷移を追加する

Important
状態名は後まわしにする

状態を配置すると、状態名をつけたくなります。ですが、状態名をつけるのは後まわしにしましょう。 なぜなら、どのような状態なのかを決定する要因は、状態名ではなく、イベントやアクションだからです。 そして、イベントやアクションが定まったら、イベントやアクション(あるいはそれらの元となる仕様上の処理の名前など)を元に状態名を命名します。

ステートマシン図を使って振る舞いを考える時、最初に考えるのは「何が起きるのを待っているのか」です。 ユースケース記述「 [use_case_desc08] を読むと、ゲームを始めた段階では、「1投目のピン数を受け取るのを待っている」と考えればよさそうです。

まず、ステートマシン図に状態と状態遷移を追加しましょう。

イベント「ピン数を受け取った」を追加する
  1. ステートマシン図に状態を2つ追加する。

    • パレットから「状態」のシンボルを選択して、図に配置する( 状態と開始疑似状態を追加した )。

    • 一方の状態には、最初の状態だとわかるよう「開始疑似状態」をつける。

{half-width}
Figure 50. 状態と開始疑似状態を追加した
  1. 状態遷移を追加する。

    • パレットから「遷移」のシンボルを選択して、一方の状態にマウスカーソルを移動する。

    • 青い枠が現れたら、マウスをクリックし、そのままドラッグする( 状態と状態の間に遷移を追加する )。

    • 折り曲げたいときは途中でもマウスをクリックする。

    • もう一方の状態の中にマウスを移動して青い枠が現れたらマウスを離すと、「遷移」が引かれる( 状態と状態の間に遷移を追加した )。

{half-width}
Figure 51. 状態と状態の間に遷移を追加する
{half-width}
Figure 52. 状態と状態の間に遷移を追加した

イベントを追加する

状態遷移が追加できたら、最初の状態で起きるのを待っているできごとを考えて、イベントとして追加しましょう。

最初の状態では、投球したピン数を受け取るのを待っていますね。 そして、状態が遷移するときは、既に(いままさに)受け取ったときになります。 そこで、イベントとして表すときには「ピン数を受け取った」のように表します。 「受け取る」とすると、まだ受け取るのを待っているところなのか、もう受け取った後なのかはっきりしません。 あるいは、「渡す(渡した)」としてしまうと「GameScore」クラスがピン数を渡すとみなされてしまいます。 イベント名を考えるときは、動作の主体やイベントが「起きた」といった時制を意識してみてください。

イベント「ピン数を受け取った」を追加する
  1. 追加した遷移を選択した状態で、プロパティーの「トリガー」を編集して「ピン数を受け取った」とする( イベント「1投目のピン数を受け取った」を追加した )。

  2. 受け取るピン数がわかるよう、ピン数をパラメーターとして追加しておいた。

{three-quarters-width}
Figure 53. イベント「1投目のピン数を受け取った」を追加した

アクションを追加する

次に考えるのは、イベントが起きたときに実行したい処理(アクション)です。 1投目のピン数を受け取るのを待っているときにやりたい処理は、ユースケース記述「 [use_case_desc08] 」の「現在の状態が、1投目のピン数入力待ちのとき」に書いてある処理です。 アクションに記載する処理と次の状態への遷移がわかるよう、抜粋した記述に説明を追加しておきました( 「現在の状態が、1投目のピン数入力待ちのとき」の処理(ユースケース記述より抜粋) )。

「現在の状態が、1投目のピン数入力待ちのとき」の処理(ユースケース記述より抜粋)
  1. ゲームスコアに新しいフレームを追加する。(アクションでやりたいこと)

  2. 現在のフレームの1投目にピン数を記録する。(アクションでやりたいこと)

  3. 1投目がストライクだったときは、現在の状態を次の1投目のピン数入力待ちに変更する。(次の状態への遷移1)

  4. ストライクでなかったときは、現在の状態を2投目のピン数入力待ちに変更する。(次の状態への遷移2)

ステートマシン図にアクションを書くときは、このユースケース記述をそのまま書くのではなく、まず「操作に追加してそれをアクションとして呼び出す」方法を原則としましょう。

Note
アクションを操作にしてから書く理由
  1. その処理に名前をつけることです。名前をつけることで、まとまりのある処理と捉えやすくなります。

  2. 再利用の促進です。操作にしておけば、他の場面でも呼び出して使えます。

この演習において、同じ処理が他の状態に現れる場面があるのかはっきりしていませんが、原則に従って作成してみます。

操作の名前は、「1投目のピン数入力待ちのとき」のアクションという意味を込めて「first_action」としましょう。

「1投目のピン数入力待ちのとき」のアクションを追加する
  1. 「Frame」クラスに、操作「first_action」を追加する( Frameクラスに操作「first_action」を追加した )。

    • クラス図を開き、 Frame クラスに操作を追加する(操作を追加する手順は省略)。

{half-width}
Figure 54. Frameクラスに操作「first_action」を追加した
  1. 追加した操作をアクションに追加する( アクション「first_action」を状態に追加した )。

    • アクションを追加したい状態を選択して、プロパティーの「入場/実行/退場」タブを選択する。

    • 「入場動作」に「first_action」と入力する。

    • マウスカーソルを図に戻すと、入力が反映される。

    • ノートを追加して、アクション詳細を書いておいた。

{full-width}
Figure 55. アクション「first_action」を状態に追加した

そして、「 「現在の状態が、1投目のピン数入力待ちのとき」の処理(ユースケース記述より抜粋) 」の後半部分、次の状態への遷移を追加します。 記述をみると状態遷移は2つあります。 まず、考えやすいストライクでなかったときを考えていましょう。 このときは、2投目のピン数を受け取るのを待つことになりますので、そのための状態遷移とイベントを追加します。

2投目のピン数入力待ちに遷移する状態遷移を追加する( 2投目のピン数入力待ちに遷移する状態遷移を追加した )。
  1. 新しい状態を追加する。

  2. 追加した状態へ向かう状態遷移を追加する。

{three-quarters-width
Figure 56. 2投目のピン数入力待ちに遷移する状態遷移を追加した

ストライクのときの状態遷移を追加する

では、ストライクだったときはどうしたらよいでしょうか。 このときは、「1投目のピン数入力待ちのとき」のアクションを実行した上で、次のフレームの1投目のピン数を受け取るのを待つことになります。 つまり、前の状態に戻るということです。 この状態遷移を追加してみましょう。

  1. 最初の状態への状態遷移を新しい状態を追加する。

  2. 追加した状態へ向かう状態遷移を追加する。

{full-width]
Figure 57. 次の1投目のピン数入力待ちに遷移する状態遷移を追加した(期待通りに動作しない)

この状態遷移には、待っているイベントがありません。 イベントが書いていない遷移は、遷移元の状態におけるアクションが実行された後、イベントが発生していなくても状態が遷移するという意味になります。 このような状態遷移を「自動遷移(ラムダ遷移)」といいます。

しかし、この自動遷移があると、2投目のピン数を受け取るのを待っているにもかかわらず、すぐこちらの状態遷移で遷移してしまいますね。 これは期待している動作ではありません。 つまり、ストライクのときとそうでないときは、状態処理は同じですが区別する必要がありそうです。 区別するということは、2投目を待つ場合と、ストライクで次のフレームを進む場合は別の状態と考えるわけです。

同じイベントを受け取ったときに、条件によって遷移先を変えたい場合は、イベントにガード条件を追加します。 ここでは、「ストライク」の場合と「ストライクでない」場合です。

ストライクかどうかで状態遷移を区別する
  1. ストライクのきの状態を追加する( ストライクのときのために状態を追加した )。

    • 新しい状態を追加する。

    • 最初の状態から追加した状態へ状態遷移を引く。

    • 追加した状態から最初の状態へ状態遷移を引く。

{full-width]
Figure 58. ストライクのときのために状態を追加した

イベントとアクションの追加を繰り返す

引き続き、ユースケース記述「 [use_case_desc08] 」を参照して、イベントとアクションを追加します。

1投目のピン数を受け取った状態では、起きるのを待っているできごとは「2投目のピン数を受け取る」ことです。 イベントとしては「2投目のピン数を受け取った」になります。

ふたたび、操作と状態と状態遷移を追加して、このイベントを追加します。 追加する操作は、「roll_second」としましょう。

アクション「2投目のスコアを計算する」を追加する
  1. 「Frame」クラスに、操作「roll_second」を追加する( <<class21→> )。

{half-width}
Figure 59. Frameクラスに操作「roll_second」を追加した
  1. イベントとアクションを追加する( 2投目のピン数入力待ちに遷移する状態遷移を追加した )。

    • 状態と状態遷移を追加する。

    • 状態遷移に、イベント「2投目のピン数を受け取った」を追加する。

    • 状態に、アクション「2投目のスコアを計算する」を追加する。

    • ノートを追加して「Frameクラスの roll_second を使う」と書いておく。

{three-quarters-width}
Figure 60. アクション「2投目のスコアを計算する」を追加した

そして、もし、ストライクやスペアの処理を勘案しないのであれば、この後は最初の状態に戻ればよいですね。 その場合、ステートマシン図は「 <<stm08-→> 」のようになるでしょう。

{three-quarters-width}
Figure 61. 最初の状態に戻る自動遷移を追加した

この状態遷移にはイベントがありません。このような状態遷移を「自動遷移(ラムダ遷移)」といいます。 自動遷移の場合、前の状態のアクションが実行された後、イベントが発生していなくても状態が遷移します。

これで、毎フレーム必ず2投するのを延々繰り返すように動作するステートマシン図ができました。

ストライクの処理を追加する

「1投目のピン数を受け取った」後の処理の詳細は、ユースケース記述「 [use_case_desc08] 」の「1投目のスコアを計算する」に書いてあります。これを「 1投目のスコアを計算する処理の詳細 」に抜粋しておきます。

1投目のスコアを計算する処理の詳細
  1. ゲームスコアに新しいフレームを追加する。

  2. 現在のフレームの1投目にピン数を記録する。

  3. 1投目がストライクだったときは、現在の状態を次の1投目のピン数入力待ちに変更する。

  4. ストライクでなかったときは、現在の状態を2投目のピン数入力待ちに変更する。

ストライクのときのために状態を追加した 」には、1投目で10ピン倒してストライクになったときの処理が反映できていません。 反映するには、ステートマシン図をどのように修正すればよいでしょうか。

まず、1投目のピン数を受け取ったら1投目のスコアを計算するのは変わりありません。 そして、受け取ったピン数が10ピンであったときは、2投目のピン数を受け取るのを待たずに次のフレームへ進みます。 そのために、たとえば「 ストライクのときの状態遷移の検討(1)(期待通りに動作しない) 」のような状態遷移を追加してみたらどうでしょうか。

{three-quarters-width}
Figure 62. ストライクのときの状態遷移の検討(1)(期待通りに動作しない)

一見、これでなんとかなりそうに思えます。 ところが、ここに自動遷移を書いてしまうと、1投目のスコアを計算した後は必ずこの自動遷移へ向かいます。 ここでは、2投目のピン数を受け取るのも待たなくてはなりませんが、いつもそれよりも先に自動遷移してしまうでしょう。 つまり、ここに自動遷移を書いてしまうと、2投目のピン数を受け取るイベントが来るのを待たなくなってしまうのです。

それでは、ストライクで次のフレームへ進むために、別の状態を追加してみます( ストライクのときの状態遷移の検討(2)(あいまいさが生じる) )。

{three-quarters-width}
Figure 63. ストライクのときの状態遷移の検討(2)(あいまいさが生じる)

これで、「2投目のピン数を受け取るのを待つ」ところには影響がなくなりました。 自動遷移によって次のフレームの1投目を待つところへ戻るのも問題ないでしょう。 しかし、この図では「1投目のピン数を受け取った」という同じイベントで、2つの遷移先ができてしまっています。 このように、同じイベントを待つ2つの状態遷移があると、どちらへ遷移するのかあいまいになってしまいます。 このままでは、どのように動作するのか決められません。

この問題を避けるには、1投目のピン数を受け取ったときに、それがストライクなのか調べて区別すればよいでしょう。 そこで、イベントにはイベントが起きたときに評価する条件を追加できるようになっています。 この条件のことを「ガード条件」と呼びます。

Important

ガード条件は、イベントを待っているときではなく、イベントが起きたときに評価されることを注意してください。

まず、ストライクか調べて真偽を返す操作を追加しましょう。 追加する操作の名前は「strike?」としましょう。

Tip

Rubyでは、真偽を返す操作(Rubyではメソッドと呼びます)の末尾に「?」をつける習慣があります。

ストライクのときの状態と状態遷移を追加する
  1. 「Frame」クラスに、操作「strike?」を追加する( Frameクラスに操作「strike?」を追加した )。

    • 「strike?」を選択した状態で、プロパティーのベースタブの「返り値」のプルダウンメニューから「boolean」を選択する。

{full-width}
Figure 64. Frameクラスに操作「strike?」を追加した
  1. イベントにガード条件を追加する( イベントにガード条件を追加した )。

    • イベント「1投目のピン数を受け取った」を選択した状態で、プロパティーの「ガード」欄に「ストライクではなかった」と書く。

    • マウスカーソルを図に戻すと、図上のイベントにガード条件が追加される。

    • 状態遷移にノートを追加して「Frameクラスの strike? を使う」と書いておく。

    • 前からあった状態遷移の方イベントは「1投目のピン数を受け取った[ストライクではない]」に変更する。

    • 追加した状態のアクションに「1投目のスコアを計算する」を追加する。

{full-width}
Figure 65. イベントにガード条件を追加した
  1. ストライクのときの状態を状態遷移を追加する( ガード条件を使ってストライクのときの状態と状態遷移を追加した )。

    • (まだ追加していなかった場合)ストライクのときのための状態と状態遷移を追加する。

    • 追加した状態遷移に、イベント「1投目のピン数を受け取った[ストライク]」を追加する。

    • 追加した状態のアクションに「1投目のスコアを計算する」を追加する。

{three-quarters-width}
Figure 66. ガード条件を使ってストライクのときの状態と状態遷移を追加した

これで、同じイベントが起きても、ガード条件の評価結果が異なれば別の状態へ遷移できるようになりました。

サービスフレームの処理を追加する

最終の第10フレームは、3投目を投球できます(サービスフレームと呼ぶこともあるそうです)。 この処理に対応するにはどうしたらよいでしょうか。

まず、最終フレームの3投目のピン数を保持するために、3投できるような Frameクラスを用意しましょう。 これは、 Frame クラスを継承すれば作成できそうです( 「ServiceFrame」クラスを追加し、GameScoreクラスに属性「size」を追加した )。

そして、最終フレームを追加するのは、通常のフレームと同じように操作 add_frame に任せましょう。 その代わり、現在のフレームが何フレーム目なのかわかるよう、GameScoreクラスに作成しているフレームの数を覚えておく属性「size」を追加します。

{half-width}
Figure 67. 「ServiceFrame」クラスを追加し、GameScoreクラスに属性「size」を追加した

スペアの判定を追加する

1投目が10ピンでない場合で、2投目で残りのピンをすべて倒した場合は「スペア」になります。 Frameクラスに、スペアかどうか判定する操作「 spare? 」を追加しておきましょう( Frameクラスに操作「spare?」を追加する )。

{half-width}
Figure 68. Frameクラスに操作「spare?」を追加する

状態名をつける

[stm06_svg] について、イベントとアクションが決まったので、これらを元に状態名をつけます。 状態名をつけるときは「 <<state_naming_rules> 」のような原則を使ってつけます。

状態名をつけるときの原則
  1. 待っているイベントやそのイベントを期待する状況を使って「〜待ち」とする

  2. 実行中のアクティビティやそのアクティビティが関連する状況を使って「〜中」とする

  3. 最後の状態は「〜待ち」や「〜中」とはつけず、「終了」完了」「到着」などとする

状態名は、待っているイベントのうち最も期待しているイベント元に「〜待ち」とつけます。 ところが、イベント名そのものを使うと、複数の状態が同じイベントを待っていると状態名が重複してしまいます。 そこで、仕様やユースケース記述を参照して、そのイベントが起きるときの状況を表わしている名前を探して状態名にします。 たとえば「ピン数受け取り待ち」などとなるでしょう。

もし、イベントの発生を待っている間、継続して実行している処理があれば、おそらく「do アクティビティ」の処理として書いてあるでしょう。 この処理を元に「〜中」とつける方法もあります。 この場合も、アクションそのものではなく、仕様やユースケース記述からそのアクションを実行している状況を示している名前を参照してつけます。 たとえば「2投目投球中」などとなるでしょう。

Warning

状態名に「待機中」を使う場合は、要注意です。 対象業務の業務用語として「待機中」が定義されている場合は、そのときを表す名前として使うこともできるでしょう。 これは、業務の用語であるなら、その「待機」が何を待っているのかについても業務として明らかなことが多いからです。 ですが、それ以外の場合は、再考してみた方がよいでしょう。 たいていの場合、待っているということそのものよりも「待っているイベント(や、そのイベントを期待する業務上の状況)は何か」の方が重要です。

それでは、状態名をつけてみましょう。 1投目については、同じイベントに対する別の状態遷移と状態を追加します( 状態名をつけた )。

{full-width}
Figure 69. 状態名をつけた