本屋さんのクラス設計
本屋さんですから、まずは「本」クラスが必要です。「本」クラスは、下記のようないろいろな属性を持ちます。
- 題名
- 著者
- 発行者
- 値段
- 重さ
- ページ数
次に、本を購入するための「会計システム」を作りましょう。
レジでお金を払う時は、購入したい本を店員さんに渡すと、「○○円になります」と言われます。そこで、本の値段から税込価格を計算するメソッドを用意しましょう。
また、この本屋さんでは、購入した本を宅配便で送ってもらうこともできます。そこで、「宅配システム」も作りましょう。
宅配便で送るためには、別途、送料が必要となります。そこで、本の重さから送料を計算するメソッドを用意しましょう。
「会計システム」も「宅配システム」も、本当はもっとたくさんの機能が必要ですが、とりあえずは、ここまでにしておきましょう。
本屋さんのクラス設計は、次の通りになりました。
これをオブジェクト図で表すと、次の通りになります。
インターフェースを導入しよう
ところで、さきほど作った2つのメソッド
- 税込価格を計算する
- 送料を計算する
は、どちらも、引数に「本」クラスを渡していました。しかし、これらのメソッドは、本当は、「本」に限定されたものではありません。
「税込価格を計算する」メソッドは、値段がついているものなら、「本」に限らず、あらゆるものについて税込価格を計算できます。
例えば、この本屋さんでは、本だけでなく、CDやDVDも置いておきたくなるかもしれません。その時、「会計システム」クラスに
- CDの税込価格を計算する
- DVDの税込価格を計算する
というメソッドを追加する、というのは賢くありません。
そこで、インターフェースを導入します。
「税込価格を計算する」メソッドは、値段がついているものなら何でも、税込価格を計算できます。そこで、本やCD、DVDなどを抽象化した、「売物」というインターフェースを作りましょう。「売物」インターフェースは、ただ、値段を返すメソッドだけを持ちます。
「宅配システム」についても同様です。重さが分かれば送料が計算できますので、重さを返すメソッドだけを持つ、抽象的な「荷物」インターフェースを作ることにします。
インターフェースを導入したことで、本屋さんのクラス設計は、次の通りになりました。
役割としてのインターフェース
インターフェースを導入したことで、2つのメソッド
- 税込価格を計算する
- 送料を計算する
の引数は、抽象的な「売物」「荷物」になりました。ですが、これらのメソッドに渡されるのは、実際には「本」クラスのオブジェクトです。
抽象的なインターフェースを導入したことで、クラス図は複雑になりましたが、オブジェクト図で表すと、実は何も変わっていません。
ところで、このオブジェクト図では、「会計システム」と「宅配システム」のどちらも、「本」オブジェクトとリンク(クラス図では関連)の線で結びついています。しかし、「本」オブジェクトは、「会計システム」と「宅配システム」で、同じように利用される訳ではありません。
「会計システム」の「税込価格を計算する」メソッドは、「本」が持つ属性のうち、値段だけを利用します。本の題名や著者、重さなどは、いっさい意味がありません。この場面では、「本」は値段を持つ「売物」としての役割だけを果たします。
「宅配システム」の「送料を計算する」メソッドについても同様です。この場面では、「本」は重さを持つ「荷物」としての役割だけを果たします。
つまり、オブジェクトは、場面ごとに役割が変わるのです。これを表すため、オブジェクト図ではリンク(クラス図では関連)に役割名(ロール名)を記述します。
このオブジェクト図を、さきほどのクラス図と見比べると、次の結論が導かれます。
インターフェースとは、オブジェクトの役割を表したものである。
この本屋さんでは、将来、CDやDVDも置きたくなった場合に備えて、「売物」や「荷物」といったインターフェースを導入しました。しかし、本だけしか置いていなければ、これらのインターフェースが無駄になる、という訳ではありません。
本屋さんのシステムでは、「本」オブジェクトは、「会計システム」に対しては「売物」の、「宅配システム」に対しては「荷物」の役割を果たします。インターフェースを作ったことで、これが設計書の上だけでなく、ソースコードでも表現されています。
究極のインターフェース指向設計とは
オブジェクト図では、役割名(ロール名)を記述せずに、オブジェクトどうしをリンク(クラス図では関連)の線で結ぶだけのこともあります。ですが、記述が省略されている場合でも、オブジェクトは何らかの役割を果たしています。
役割名(ロール名)を省略しなかったら、どうなるでしょうか。さきほど述べたように、オブジェクトの役割は、インターフェースとして表現されます。ですので、オブジェクトどうしのリンク(または、クラスどうしの関連)の数だけ、インターフェースができることになります。
引数を持つメソッドは、オブジェクトどうしのリンク(または、クラスどうしの関連)を作ります。例えば、
void ClassA::functionX ( ClassB* objectB );
というメソッドは、下記の図の通りにオブジェクトを結び付けます。
ここで、引数として渡されるobjectBオブジェクトは、functionXメソッドにおいては、ClassBとしてのすべてを発揮する訳ではありません。あくまで、functionXメソッドが求める役割を果たすだけです。
その役割を「functionXにおけるBの役割」という役割名(ロール名)で表し、インターフェースで表現すると、次のようになります。
そして、functionXメソッドの定義は、下記の通りになります。
void ClassA::functionX ( functionXにおけるBの役割* roleB );
この考え方を突き詰めると、究極的には、
すべてのメソッドの引数は、具体的なクラスではなく、抽象的なインターフェースにすべきである。
という結論になります。