オブジェクト・プログラミング 第12回 状態遷移図 シーケンス図
今日の目標 オブジェクトの状態について人に説明できるようになる。 オブジェクト間の相互作用について人に説明できるようになる。 「状態遷移」とは何か、人に説明できるようになる。 状態遷移図(UML)を書けるようになる。 状態遷移図を元に、Javaでプログラムを書けるようになる。 オブジェクト間の相互作用について人に説明できるようになる。 なぜ、相互作用図が必要か、人に説明できるようになる。 簡単な相互作用図(シーケンス図)を書けるようになる 相互作用図を元に、Javaでプログラムを書けるようになる。
今日作る自動販売機の仕様 ①自動販売機ソフトウエアを起動する。 ②この時点では、「コーラ」のボタン(VMButton)は 押せない状態になっている。 ③「100円投入」ボタンを押す。 ④投入総額ウインドウ(CoinDeposit)に100円と表示される。 ⑤「コーラ」のボタンが押せる状態になる。 ⑥「コーラ」のボタンを押す。 ⑦商品が排出口(ProductOutlet)に排出される。 ⑧投入総額ウインドウに0円と表示される。 ⑨「コーラ」のボタンが押せない状態になる。
ボタンの状態を変える 今日の課題では、状況によって、ボタンの状態を変化させる必要があります。 ボタンの状態は2つあります。 On状態 Off状態
状態遷移図 イベント・ドリブン型のプログラミングでは、イベントが送られて来るたびに、何かの処理がなされ、オブジェクトの状態が変化します。 これを図解するのがUMLの状態遷移図です。 状態遷移図を書かないで、プログラミングすると、不正入力が起こりやすくなり、そのバグ修正が困難になります。
状態遷移図とは? あるオブジェクトの状態の変化を表した図 例えば、右の図は、 開始状態 状態A 状態A 状態B 状態B 終了状態 と、状態が変化していくことを表している
今回のボタンの状態遷移図
記号の意味1(開始状態) 開始状態 一番最初の状態 黒丸で表す
記号の意味2(終了状態) 終了状態 一番最後の状態 中黒の二重丸であらわす
記号の意味3(状態) 状態 一つの状態を表す 角丸四角の中に状態の名前を書く Off状態 On状態
記号の意味4(遷移) 状態遷移 ある状態から別の状態に変化する(遷移する) ことを表す 矢印で表す Off状態 On状態
記号の意味5(イベント) イベント 状態遷移のきっかけとなるイベント 投入総額変更[ 対象アイテムの価格<=投入総額 ] Off状態 On状態 投入総額変更というイベントがきたら、 Off状態はOn状態へと遷移するという意味
記号の意味6(ガード条件) イベントが来たときに、条件を満たしたときだけ、遷移する場合 [ ]内に書く 投入総額変更[ 対象アイテムの価格<=投入総額 ] Off状態 On状態 投入総額変更というイベントがきて、 かつガード条件を満たしたときだけ、 Off状態はOn状態へと遷移するという意味
記号の意味7(アクション) イベントが来て、状態が遷移する場合に、何らかの処理を行なう場合。 イベント名と「/」記号で区切る マウスクリックイベント が起こった場合、 商品を排出して、状態を 遷移するという意味。
記号の意味8(イベント送信) イベントが来て、状態が遷移する場合に、他のオブジェクトにイベント送信処理を行なう場合。 「^」記号の後に、送信イベント名を書く マウスクリックイベント が起こった場合、 商品を排出して、 商品販売というイベント を送信して、状態を 遷移するという意味。
今回のボタンの状態遷移図 もう一度確認してみましょう
状態遷移図をマスターせよ 状態遷移図には、他にもいろいろな記号があります。 詳しくは、UML記法の専門書を見てください。 状態遷移図を制するものは、イベント・ドリブン型プログラミングを制します。
状態遷移図だけではわからない 何処からイベントが送られてくるのですか? 何処へイベントを送るのですか?
オブジェクト間の相互作用を考える。 シーケンス図 状態遷移図だけではわからない、複数のオブジェクトのイベントの送受信の関係を記述する図 時系列にイベントの送受信の関係がわかる。 次ページのような図です。
シナリオごとに書く シーケンス図は、一枚で、すべての相互作用を表わせません。 シナリオごとに、シーケンス図を書きます。 全ページの例は、「100円ボタンが押された」時のシナリオです。
記号の意味1(登場オブジェクト) そのシナリオで登場するオブジェクトを一番上に書きます。 ユーザは別の記号で表現します。 カルピス : VMButton 「オブジェクト名」:「クラス名」
記号の意味2(イベント) イベントの送信者から、 受信者へ矢印を引きます。 上にイベント名を書きます。
記号の意味3(時系列) 上から下へ、 時系列に相互作用が 起こります。 100円投入ボタンは、 マウスクリックイベント を受け取って、 100円投入イベントを 送信します。
記号の意味4(活性化) 活性化とは、 そのオブジェクトが、 制御対象に なっていることを示す。 活性化している 活性化していない
「投入金額変更」イベントは、CoinDepositオブジェクト から、呼ばれていることがわかる
状態遷移図+シーケンス図 状態遷移図にシーケンス図を組み合わせることによって、よりわかりやすい設計図となる。 どちらから書いてもよい。 シーケンス図は、シナリオごとに、複数書けるので、必要なシナリオを選んで書くべきである。(すべての状態ごとに書いていたら、膨大な数になってしまう。)
設計図にしたがってプログラムを書く 実装編
実装の仕方 今回の設計図で、実装の仕方は何通りもあります。 今回は、一番簡単な方法を紹介します。 まずは、前回の実装の問題点 解消からです→次へ
前回の実装の問題点(1) イベントハンドラを全部VendingMachineFrameに書いていた。 void vMButton1_mousePressed(MouseEvent e) { productOutlet1.getModel().addItem(new Item(itemInfo1)); } void vMButton2_mousePressed(MouseEvent e) { productOutlet1.getModel().addItem(new Item(itemInfo2)); これだと、ボタンが増えるたびにハンドラが増え、 VendingMachineFrameクラスが肥大化してしまう
VMButtonに イベントハンドラを持たせよう! 本来、ButtonのハンドラはButtonに書かれるべきです。そうすれば、前ページの問題は解消されます。
VMButtonにハンドラを登録する 前回、Frameで登録したのとほぼ、同じ方法でできます。 今回は、VMButton.javaを開く 「設計」タブをクリックする 「イベント」タブをクリックする 「MouseClicked」をダブルクリックする
VMButtonクラスの問題点(2) 現在のVMButtonクラスでは、 ItemInfoとの対応がとれません。 ItemInfoとVMButtonの対応をつけ られるように変更します。
前回までのVMButtonクラス //はじめの部分を抜粋 public class VMButton extends JComponent { private VMButtonModel model;//ボタンのModelインスタンスを //保存する変数 //コンストラクタ public VMButton() { model = new VMButtonModel(this);//ボタンModelの //インスタンスを生成する setUI(new VMButtonUI());//ボタンViewを生成して設定する }
ItemInfoとの対応付けするための変数とメソッドを用意する。 //今日変更するVMButtonクラス public class VMButton extends JComponent { private VMButtonModel model;//ボタンのModelインスタンスを private ItemInfo itemInfo;//対象となるiteminfoを保存する変数 //コンストラクタ public VMButton() { //省略 } //対象となるiteminfoを設定するメソッド public void setItemInfo(ItemInfo newItemInfo){ itemInfo = newItemInfo;
Frameクラスに対応付けるメソッドを呼ぶコードを加える //Jbuilderが生成したコードを抜粋 //VendingMachineFrameクラス private void jbInit() throws Exception { itemInfo1.setBounds(new Rectangle(48, 61, 60, 90)); itemInfo2.setItemName("calpis"); itemInfo2.setItemPrice(200); itemInfo2.setBounds(new Rectangle(115, 62, 60, 90)); vMButton1.setBounds(new Rectangle(56, 181, 43, 20)); vMButton2.setBounds(new Rectangle(123, 181, 43, 20)); vMButton1.setItemInfo(itemInfo1);//ボタンと商品の対応付けをする vMButton2.setItemInfo(itemInfo2);//ボタンと商品の対応付けをする
さて、設計図を実装しましょう。 まず、ここを考えます。
イベントを送受信する仕組みを作ります。 やり方はいろいろありますが、 ここでは、簡単に、 メソッドを呼び出す。 という方針で実装します。
イベントを受け取るメソッドを作ります。 送信元の CoinDepositを引数に 取ります。 //投入総額が変更されたときに受けるイベントのハンドラ //VMButtonクラス public void amountChange(CoinDepositModel cdModel){ //現在が off 状態だった場合 if(!model.isOn()){ //投入金額が対象商品の価格以上だったら、 on 状態にする。 if(itemInfo.getModel().getPrice() <= cdModel.getAmount()){ model.setOn(true); } //現在が on 状態だった場合 else{ //投入金額が対象商品の価格より小さかったら、 off 状態にする。 if(itemInfo.getModel().getPrice() > cdModel.getAmount()){ model.setOn(false);
イベントを送信する仕組みを作ります。 いつ、送信すべきか? →お金の総額が変わったときですね。 それはどこか? →CoinDepositModelクラスのaddMoney()メソッドですね。 誰に送信すべきか? →存在するVMButtonすべてに送信してあげる必要がありますね。
イベントを送信する ※コメントは省略 これでは、 誰に送信すればいいのか わかりません! 今存在するVMButtonを 知りたいですね。 public class CoinDepositModel { private int amount; private CoinDeposit controller; public CoinDepositModel(CoinDeposit initController){ controller = initController; } public void addMoney(Money money){ amount = amount + money.getValue(); controller.repaint(); //ここにイベントを送信するプログラムを書く public int getAmount(){ return amount; public Money undo(){ //どうしたらできるか考えてみよう! ※コメントは省略 これでは、 誰に送信すればいいのか わかりません! 今存在するVMButtonを 知りたいですね。
いちばん簡単にVMButtonを知る方法 FrameクラスのVMButtonをstaticにします。 public class VendingMachineFrame extends JFrame { BackgroundPanel backgroundPanel1 = new BackgroundPanel(); ItemInfo itemInfo1 = new ItemInfo(); ItemInfo itemInfo2 = new ItemInfo(); static VMButton vMButton1 = new VMButton(); static VMButton vMButton2 = new VMButton(); CoinDeposit coinDeposit1 = new CoinDeposit(); ProductOutlet productOutlet1 = new ProductOutlet(); これで、クラス変数になるので、何処からでもボタンのメソッド を呼び出せるようになります。
イベントを送信するためにメソッドを呼ぶ。 実引数に 自分自身を 渡します。 //CoinDepositModelクラスから抜粋 public void addMoney(Money money){ amount = amount + money.getValue(); controller.repaint(); //ここにイベントを送信するプログラムを書く VendingMachineFrame.vMButton1.amountChange(this); VendingMachineFrame.vMButton2.amountChange(this); } クラス変数を呼び出すときは、 「クラス名」.「変数名」で何処からでも呼び出すことができます。
staticを使うとき 考えねばならぬこと 今回は、簡単に実装をするために、staticな変数を使い、何処からでも参照できるようにします。(global変数といいます。) ですが、この方法は、オブジェクト指向では、推奨されません。第10回でやったように、データはカプセル化されるべきです。 ですから、むやみに使ってはいけません。使わなくても必ずできる方法があるはずです。
これで、ボタンがon,offする ここまでやると、投入金額を増やし、価格を超えるとボタンがonになり、価格を下回るとoffになるはずです。 実験してみましょう。 実験するために「100円投入」ボタンのほかに、「100円削除」ボタンを作るとよいかもしれません。
今回の課題1 CoinDepositクラスの状態遷移図を書きなさい。 VMButtonがOnの状態で、マウスがクリックされたときの、シーケンス図を書きなさい。 手書きで書いて、授業の終わりまでに提出してください。
今回の課題2 前回のプログラムを、一番初めに提示した、仕様を満たすように実装しなさい。 次の設計図を元に実装すればうまくいくはずです。 授業で提示した状態遷移図+シーケンス図 課題1で書いた状態遷移図+シーケンス図 どうしても実装が苦手な人は、授業で提示したところまででも一応受け付ける。
提出方法 objprog-12@crew.sfc.keio.ac.jp宛て。 ただし、課題(2)のソースは、VendingMachineFrame, VMButton, CoinDepositModel, CoinDepositだけでよい。 (感想を任意でお願いします。) Subjectはログイン名を必ず書いてください。 ex) t96844ym 余計な[]などをつけないでください。 来週の日曜(24:00)まで。 毎週の課題は今回で最後ですので、がんばってください!