|
じゃんけんのクラス抽出 |
||||||||||||
クラスはひとつ、オブジェクトはたくさん | ||||||||||||
じゃんけんのクラスを決めていきましょう。やはり、Mastermind の時と同じように登場人物をオブジェクトと考えていきましょう。ただ、Mastermind と違うのは登場人物の 1 人であるプレイヤーは複数いるということです。 しかし、よく考えてみるとプレイヤー A とプレイヤー B は名前は違うだけで行う動作はまったく同じです。ということはプレイヤー A とプレイヤー B は同じクラスで定義することができます。プレイヤー A とプレイヤー B はクラスから生成した異なるオブジェクトと考えるわけです。 そうすると、じゃんけんで登場するクラスは
の 2 つになります。 クラスの定義を行う前に、2 つのクラスのオブジェクトの関係を考えてみましょう。シーケンス図からオブジェクトの関係がある程度導き出すことができます。シーケンス図は大活躍ですね。 オブジェクト指向ではクラス間の関係として次にあげる 4 種類の関係を考えます。
関連はあるオブジェクトに他のオブジェクトが結びつくことです。たとえば、あるオブジェクトにメッセージセンディングをするときは、相手のオブジェクトが知らなくてはできません。この相手のオブジェクトのことを知っているということが関連をもつということになります。 シーケンス図から導き出すことができるのは、この関連です。 汎化はあるクラスをより具体的なものにしたときの関係を指します。たとえば、動物というクラスに対して、犬というクラスは汎化の関係にあります。犬は動物の一種なのですが、動物のある種をより具体的に表したものだからです。この関係を is a kind of と呼ぶこともあります。先ほどの例だと 「犬」 is a kind of 「動物」ですね。 Java だと汎化は extends で実装します。 次の実現はある操作を定義しているクラスと実際にその操作を実装したクラスとの間の関係になります。Java ではインタフェースを implements するときがこの実現に相当します。 依存はあるオブジェクトが他のオブジェクトを使用するときに当てはまります。 ここではじゃんけんで使われるクラス間の関連を考えていきます。 図 2-3 のシーケンス図で Judge オブジェクトは Player オブジェクトに対して「手の問い合わせ」を行っています。ということは、Judge オブジェクトは何らかの方法で Player オブジェクトを知っていなければ、メッセージを投げることができないはずです。 この知っているということが関連に相当します。 一方、Player オブジェクトは Judge オブジェクトからのメッセージに対して答えるだけで、自分から Judge オブジェクトに問いかけることはしません。とすると、Player オブジェクトは Judge オブジェクトを知っている必要がないということです。 この関係を図に表してみましょう。ここでも、UML で定義された図を使用しましょう。クラスとクラス間の関係を表した図をクラス図といいます。このクラス図でじゃんけんに登場するクラスを描いてみましょう。 クラス図ではクラスは四角で表されます。四角の中に書いてあるのがクラス名です。クラス間に関連がある場合は、クラスを実践でつなぎます。図 2-4 ではクラスを結ぶ線に矢印が描いてあります。これは明示的に単方向の関連であるということを示しています。 前述したように Judge オブジェクトは Player オブジェクトを知っています。しかし、Player オブジェクトは Judge オブジェクトを知りません。図 2-4 では Judge オブジェクトは Player オブジェクトを知っているが、Player オブジェクトは Judge オブジェクトを知らないというということを表しているわけです。 矢印がないときには、通常双方向の関連を示します。 線の上に書いてある数字はオブジェクトが結びつく相手のオブジェクトの数を表しています。この数字は省略することが可能です。 Player の側に書いてあるのは、Judge オブジェクトが 2 つ以上の Player オブジェクトを知っているということを表しています。 数字の書き方には次のようなものがあります。
図 2-4 のように 2..* という書き方はあまりしませんが、じゃんけんは 2 人以上いないとできないので、このように記しました。 このクラス間の関連は Java で実装するときには属性として、相手のオブジェクトの参照を持たせるようにします。相手が複数の時には配列や後で説明するコレクションを使用したりします。 クラス間の関連も分かったので、それぞれのクラスについて定義していきましょう。
|
Player クラス | ||||||||||||||||||||||||
Mastermind の時と同じようにシーケンス図を利用してクラスの定義を行ってみます。
図 2-3 を見てみると、はじめに審判はプレイヤー A に手を問い合わせています (メッセージ 1)。プレイヤー A は手を選択し (メッセージ 2)、戻り値で審判に選択した手を渡します (メッセージ 3)。同じことを審判はプレイヤー B にも行います。プレイヤー B の処理もプレイヤー A とまったく同じです。審判はすべてのプレイヤーから手を問い合わせたら、判定を行います (メッセージ 7)。 このシーケンスからクラスに必要な属性とメンバ関数を引き出していきます。 Player オブジェクトは審判から手の問い合わせを受けますから、public なメンバ関数で定義することができます。関数名は getHand にしましょう。ところで、戻り値のじゃんけんの手ですが、どのように表せばいいでしょうか。いろいろ手法があるのですが、定数で「グー」「チョキ」「パー」を表しましょう。
int 型で「グー」「チョキ」「パー」を表せるため、getHand 関数の戻り値は int 型になります。
public なメンバ関数はこれだけですが、プレイヤーだけが使う private なメンバ関数も決めなくてはいけません。シーケンス図にも出てきている、手を選択するというメンバ関数が必要です。手を選択するので selectHand という名前にしましょう。戻り値は選択した手と考えるのが自然ですから、getHand 関数と同様 int 型にします。
プレイヤークラスの属性はどうでしょうか。自分が出す手を属性として持っていてもいいのですが、審判が問い合わせたときに新たに手を選択するので、属性として持っている必要はなさそうです。でも、それではつまらないのでプレイヤーの名前を属性にしてみましょう。ついでに名前を取り出すための関数 getName もメンバ関数に付け加えてみます。 これをまとめるとプレイヤークラスは次のようになります。
|
Judge クラス | ||||||||||||||||||||||||||||
次に Judge クラスに考えてみましょう。まずはメンバ関数からいきましょう。 Judge オブジェクトは他のオブジェクトからメッセージセンディングされることは図 2-3 を見てもわかるようにありません。したがって、public な関数はいらないはずです。 private なメンバ関数として図 2-3 の「判定を行う」処理があります。判定ですから judge という名前にしましょう。引数はプレイヤーの人数が決まっていれば
のようにプレイヤーごとの手を引数にしてもいいのですが、プレイヤーの人数を決まっていないときにはこれは使えません。そこで、配列にしておきましょう。
配列にしましょうといいながら List というクラス (正確には java.util.List) を使用しているのは、配列より List クラスの方が使いやすいからです。List クラスと配列を比較したときに、List クラスが使いやすいところをあげておきます。
List クラスは実際はインタフェースで、実装クラスとしては ArrayList クラスや LinkedList クラスを使用します。一般的には ArrayList が使われることが多いです。このような複数の要素を保持するためのクラスをまとめたものをコレクション API といい、Java 2 から導入されました。Java 2 以前では Vector クラスなどが使用されていましたが、List の方が高機能なので、古いバージョンとの互換性を保つ意外には Vector クラスを使用する必要性はないと思います。 では、judge 関数に戻りましょう。引数は List オブジェクトの hands です。hands の各要素はじゃんけんの手を表し、Integer クラスのオブジェクトで表されています。 戻り値は勝者のリストにしましょう。あいこの場合は勝者はいないので、からっぽのリストになります。 ところで、本当に public な関数はないのでしょうか。図 2-3 に明確に示されていませんが、じゃんけんを行うという関数があるはずです。この関数の中でプレイヤーに手を問い合わせ、判定を行うという処理を行います。ゲームを行うので playGame という名前にしましょう。引数や戻り値は特に必要ないです。 次に属性です。Judge オブジェクトが持たなくてはいけない情報にはなにがあるでしょうか。クラス間の関連を調べたときに、Judge クラスは Player クラスへの関連があることが分かりました。ということは、Judge クラスの属性には Player オブジェクトのコレクションがあるということですね。 また、問い合わせた結果も属性に入れておきましょう。 Judge クラスについてまとめたものを次表に示しておきます。
図 2-4 のクラス図にクラス名しか記述しませんでしたが、属性やメンバ関数も記述することができます。図 2-5 に一般的なクラス図の記述法を示します。
クラス表す四角を 3 つに区切って、1 番上にクラス名、2 番目に属性、最下段に操作 (メンバ関数) を記述します。属性などの型は名前の後にコロンで区切って表記します。また、属性や操作の public や protected などの可視性は次表の記号を属性や操作の前に記述することで表します。
クラス図でクラスを表すときには、属性や操作の部分は省略可能です。しかし、属性か操作の一方だけを省略するときは属性か操作か分かるように区切りの線は記述しておくほうがいいと思います。 属性や操作を図 2-4 に加えたものを図 2-6 に示しました。 気づかれた方もいらっしゃると思いますが、図 2-6 の Judge クラスの属性には players がありません。Judge クラスから Player クラスへの関連を実現しているのが、Player オブジェクトへの参照を保持している属性 players だからです。すでに関連の線で Players オブジェクトへの参照が表されているので、わざわざ属性には描かないということです。 ただし、関連の線では属性名が分からなくなるので、線の根元のところに名前を記述することもあります (これをロール名とよびます)。 この節のまとめを次に示しました。
ここまで、UML で定義された図のうち、ユースケース図、シーケンス図、クラス図が出てきました。アプリケーションを作るときには、少なくともこの 3 種類の図を (シーケンス図の代わりにコラボレーション図を使うことがありますが) 描いたほうがいいと思います。ソースを書く前に、まずこれらの図を記述することで、アプリケーションの構成や処理の流れを整理することができます。 どんな小さいアプリケーションでも欠かさず図を表す習慣を身につけましょう。 (Oct. 2000) |
|