<クラスの見極め>

前章で「クラスとは何か?」が理解できましたか? 理解できた前提で、さらに高みへ向かいます。 アプリケーションターゲットが大きい場合や複雑な場合のクラスへの落と し方です。 実は、これは組織の上長が行う作業に酷似しています。 ある程度、規模の小さい作業なら、良くできる社員1人に全てを任せて、 自分はその社員を管理するだけで良いかもしれません。 しかし、作業規模が大きくなってくると複数人に作業を分担せざるをえな くなってきます。 あなたが社長なら、複雑な作業を自分ひとりで一気に詳細まで落とすので なく、部長レベルの作業に分解するにとどまるでしょう。 結果として、 社長レベルの作業 部長レベルの作業 課長レベルの作業 平社員レベルの作業 と徐々に作業が落とされていくはずです。 この作業分担そのものが、アプリケーションターゲットをクラスへと落と す作業に他なりません。 しかし、これがアプリケーションコーディングとなると、なかなか同じこ とを実践できる人はいないものです。 良くないコーディングの1例として、メッセージハンドラに数千〜数万行 のコードを一気に書くというものがあります。 これを先の例に例えるなら、 「社長が数十〜数百人分の平社員の仕事をこなす」 というような感じになるでしょうか? この社長が体を壊してつぶれる日は目に見えていますし、そもそも体を壊 すまで頑張ったとしても無理なものは無理でしょう。 つまり、そういうコードを書くと似たような目にあうということです。 現実の「クラスの見極め」作業では.... アプリケーションターゲットをまずは大枠の機能に落とし込みます。 落とし込だら、その機能のレベルが妥当か検証します。社長から部長への 作業指示ですから、そのレベルになっているかどうかを検証します。いき なり課長レベルや平社員レベルに落ちていては駄目です。 部長レベルの機能に落とし込みが完了したら、次は個々の部長レベルの機 能を課長レベルに落とし込みます。この段階でも先と同様にきちんと課長 レベルに落とし込まれているか検証を行います。 同様にして平社員レベルにまで落とし込んだら完了です。 社長/部長/課長/平社員は全てクラスの対象となります。 (対象とだけ言ったのは、それだけでは足りないクラスもあるでしょうし 逆に部課長兼任で消滅するクラスもあるからです) また、クラス間の関連を見誤ることがあります。 特に、この誤りは犯しやすいので細心の注意を払いましょう。 クラス間の関連は大きく3種類に分かれます。 1.関連 2.集約 3.継承 1.は一方のクラスが他方を利用するだけの関係です。双方のクラスには それ以上の関係はありません。 2.は一方が他方の一部である関係です。 3.は一方が他方を部分的に絞り込んだだけの関係です。 (例) 人間クラスとの関連を考えます。 [関連] 人間::見る(テレビ); // 人間とテレビは関連がある [集約] class 人間 { 目 m_eye; // 目は人間の一部である }; [継承] class 人間 : // 人間は動物の種類の1つである public 動物 { }; 関連以外は、一方が他方を含む関係です。集約は他方をパーツとして含み ますが、継承は対象を絞り込むだけでパーツではありません。 そこで、継承をIs-aの関係,集約をHas-aの関係と呼びます。 継承:人間は動物である。(正しい) 集約:目は人間である。(誤り) これらを見誤ってもソフトウェアは作成できますが、後からソースコード を見直した際に意味不明な物になります。 人間との関連をわざと間違えてみましょう。 [集約] class 人間 { テレビ m_tv; }; 人間がテレビを含むという定義です。 ということはテレビを持っていない人は人間ではない(゚◇゚)~ガーン。 テレビを持っていない人はこんな設計をした人を殴り倒しましょう。 冗談はさておき、この設計では人間が1人1人別々のテレビを持っている ことになります。一家で1台しかテレビがない場合に、家族4人のテレビ が同じものであることをどうやって表現しましょう?また、逆に1人で2 台も3台もテレビを持っている人をどうやって表現しましょう? 分からないことだらけでしょう? ということはこんなものがソースコードになったら、ますます分からない のです。 [継承] class 人間 : public 目 { }; 確かに人間は目を持っています。その1点のみに着目すると正しいように も見えます。しかし、この定義の意味するところは逆です。広義に言うと 「人間は目に含まれる」という意味です。明らかにおかしいですね。 これがソースコード的におかしいことは次の例を挙げれば十分でしょう。 人間は鼻も持っています。鼻はどうやって扱いましょう。同じように class 人間 : public 鼻 { }; とやってみます。 どうですか?1人の人間の目と鼻は生き別れになってしまいました。 やっぱり訳が分からないでしょう。 ということはこんなものがソースコードになったら、ますます分からない のです。 さて、ここらで、1つ例を挙げて落とし込んでみましょう。 (例) ポーカーゲーム ポーカーゲームは大きく以下の要素から成ります。 1.ディーラ 2.参加者 3.トランプ この時点ですでに気づくことが一つあります。「ディーラ」と「参加者」 は同じ人間です。 人間 +- ディーラ +- 参加者 という具合にクラスの継承関係を持たせましょう。 これら全てのクラスををさらに落とし込む必要があります。 トランプには 1.数字 2.マーク(スペード/クローバー/ハート/ダイヤ) があります。 これをクラスの対象にするかどうかは意見の分かれるところですが、先の 例に例えれば平社員に全任するか、アルバイトにまで仕事をまわすかとい うぐらいの話ですので感覚で決めていいでしょう。 人間はこれ以上分解しても意味がなさそう(手や足,目に分解したところで 仕方ない)ですので、ここまでで完了とします。 この辺りで一度クラス図をおこしてみます。 +------+ | 人間 | *は1対多の関係を表す +------+ △ +---------------+ | | +----------+ +--------+ | ディーラ |-----| 参加者 | +----------+ *+--------+ ◇ ◇ |* | +----------+ * | | トランプ |--------+ +----------+ ◇ ◇----+ | | +------+ +--------+ | 数字 | | マーク | <- 上記の話のように不要なら削除してよい +------+ +--------+ (削除したらトランプクラスのメンバ変数になる) 上記のクラス図をよーく眺めてください。 ポーカーには役があります。役ができたかどうかはどのクラスが判断しま すか?現実の世界ならディーラと参加者です。でも役の概念はディーラも 参加者も同じです。 1つのクラスに役ができたかどうかの判定処理を任せてしまいたい気がし ませんか? 考え方としては2通りあります。 1)ベースクラスに任せる 2)新たなクラスを設ける 1)の場合、人間クラスに役ができたかどうかの判断を任せます。 この場合、クラス図は以下のようになります。 +----------+ +--------+ | ディーラ |-----| 参加者 | +----------+ *+--------+ | | +---------------+ | ▽ +------+ | 人間 | *は1対多の関係を表す +------+ ◇ |* +----------+ | トランプ | +----------+ ◇ ◇----+ | | +------+ +--------+ | 数字 | | マーク | +------+ +--------+ 2)の場合、カードの集まりを扱うために新たなクラスを設けます。 「デッキ」という名前にしましょう。 この場合、クラス図は以下のようになります。 +------+ | 人間 | *は1対多の関係を表す +------+ △ +---------------+ | | +----------+ +--------+ | ディーラ |-----| 参加者 | +----------+ *+--------+ ◇ ◇ ※「ディーラのデッキ」と「参加者のデッキ」 | | が意味的に全く同じものなら人間クラスとの +----------+ | 関連にした方が良い | デッキ |--------+ (言っている意味が分かりますか?) +----------+ ◇ |* +----------+ | トランプ | +----------+ ◇ ◇----+ | | +------+ +--------+ | 数字 | | マーク | +------+ +--------+ どうですか.... 同じアプリケーションターゲットでも視点を変えるだけでクラス設計は大 きく変わります。 どちらが正解というものではなく、どっちも正解です。目的に応じて使い 分けると良いです。アプリケーションターゲットがポーカーゲームだけな ら、人間クラスがポーカー役の管理をしても大して人間クラスは重くなり ませんが、将来的にブラックジャックゲームにも対応するつもりなら、デ ッキクラスにしておいた方がよいです。 なぜなら、ポーカー/ブラックジャックとも共通の処理は多く(デッキのシ ャッフル/カード分配/....)それぞれのベースクラスを設けると良いから です。 +--------+ +------+ | デッキ | | 人間 | +--------+ +------+ △ △ +-----------------+ +-----------------+ | | | | +----------+ +------------------+ +----------+ +------------------+ | ポーカー | | ブラックジャック | | ポーカー | | ブラックジャック | | デッキ | | デッキ | | をする人 | | をする人 | +----------+ +------------------+ +----------+ +------------------+ これならしっくりきますが、 こっちはちょっと無理があるでしょ? ※ クラスは一般名詞にするのが分かりやすく、「ポーカーをする人」の ような動名詞は扱いづらいものです。 (例) 「トイレ」(一般名詞)は掃除できますが、「トイレにいる人」(動名詞)は 掃除できません。できるのは大と小だけ(笑)。要するに拡張性が低い。 一般名詞のクラスを2つ用意し、関連を持たせるのが正解です。 +------+ +--------+ | 人間 |------->| トイレ | +------+ +========+ | 大() | | 小() | | 掃除() | +--------+ なお、これは概要設計にすぎませんので、現実にポーカーゲームを作ろう としたらトランプの絵(ビットマップへのパス等)やそれを表示するための ウィンドウなどなどたくさんの要素(クラスまたはメンバ変数)が必要にな るのは言うまでもありません。