究極のインターフェース指向設計

作成:2008年6月30日

吉田誠一のホームページ   >   ソフトウェア工学   >   技術コラム   >   オブジェクト指向

オブジェクト指向言語では、メソッドを定義しただけで中身を実装しない、インターフェースが登場します。

インターフェースを使うと、クラスやメソッドの再利用性が高まります。インターフェースさえ同じなら、同じコードを、いろいろなクラスに適用できるためです。

しかし、インターフェースを使うのは、コードを再利用するためだけではありません。たとえ再利用しなくても、敢えてインターフェースを作ることもあります。むしろ、再利用するか否かに関わらず、インターフェースを使うべきだ、という考えもあります。

ここでは本屋さんのシステムを例に、インターフェースの存在意義について考えてみます。

目次

  1. 本屋さんのクラス設計
  2. インターフェースを導入しよう
  3. 役割としてのインターフェース
  4. 究極のインターフェース指向設計とは

本屋さんのクラス設計

本屋さんですから、まずは「本」クラスが必要です。「本」クラスは、下記のようないろいろな属性を持ちます。

  • 題名
  • 著者
  • 発行者
  • 値段
  • 重さ
  • ページ数

次に、本を購入するための「会計システム」を作りましょう。

レジでお金を払う時は、購入したい本を店員さんに渡すと、「○○円になります」と言われます。そこで、本の値段から税込価格を計算するメソッドを用意しましょう。

また、この本屋さんでは、購入した本を宅配便で送ってもらうこともできます。そこで、「宅配システム」も作りましょう。

宅配便で送るためには、別途、送料が必要となります。そこで、本の重さから送料を計算するメソッドを用意しましょう。

「会計システム」も「宅配システム」も、本当はもっとたくさんの機能が必要ですが、とりあえずは、ここまでにしておきましょう。

本屋さんのクラス設計は、次の通りになりました。

本屋システムのクラス図

これをオブジェクト図で表すと、次の通りになります。

本屋システムのオブジェクト図

インターフェースを導入しよう

ところで、さきほど作った2つのメソッド

  • 税込価格を計算する
  • 送料を計算する

は、どちらも、引数に「本」クラスを渡していました。しかし、これらのメソッドは、本当は、「本」に限定されたものではありません。

「税込価格を計算する」メソッドは、値段がついているものなら、「本」に限らず、あらゆるものについて税込価格を計算できます。

例えば、この本屋さんでは、本だけでなく、CDやDVDも置いておきたくなるかもしれません。その時、「会計システム」クラスに

  • CDの税込価格を計算する
  • DVDの税込価格を計算する

というメソッドを追加する、というのは賢くありません。

そこで、インターフェースを導入します。

「税込価格を計算する」メソッドは、値段がついているものなら何でも、税込価格を計算できます。そこで、本やCD、DVDなどを抽象化した、「売物」というインターフェースを作りましょう。「売物」インターフェースは、ただ、値段を返すメソッドだけを持ちます。

「宅配システム」についても同様です。重さが分かれば送料が計算できますので、重さを返すメソッドだけを持つ、抽象的な「荷物」インターフェースを作ることにします。

インターフェースを導入したことで、本屋さんのクラス設計は、次の通りになりました。

インターフェースを導入した本屋システムのクラス図

役割としてのインターフェース

インターフェースを導入したことで、2つのメソッド

  • 税込価格を計算する
  • 送料を計算する

の引数は、抽象的な「売物」「荷物」になりました。ですが、これらのメソッドに渡されるのは、実際には「本」クラスのオブジェクトです。

抽象的なインターフェースを導入したことで、クラス図は複雑になりましたが、オブジェクト図で表すと、実は何も変わっていません。

本屋システムのオブジェクト図

ところで、このオブジェクト図では、「会計システム」と「宅配システム」のどちらも、「本」オブジェクトとリンク(クラス図では関連)の線で結びついています。しかし、「本」オブジェクトは、「会計システム」と「宅配システム」で、同じように利用される訳ではありません。

「会計システム」の「税込価格を計算する」メソッドは、「本」が持つ属性のうち、値段だけを利用します。本の題名や著者、重さなどは、いっさい意味がありません。この場面では、「本」は値段を持つ「売物」としての役割だけを果たします。

「宅配システム」の「送料を計算する」メソッドについても同様です。この場面では、「本」は重さを持つ「荷物」としての役割だけを果たします。

つまり、オブジェクトは、場面ごとに役割が変わるのです。これを表すため、オブジェクト図ではリンク(クラス図では関連)に役割名(ロール名)を記述します。

ロール名を記述した本屋システムのオブジェクト図

このオブジェクト図を、さきほどのクラス図と見比べると、次の結論が導かれます。

インターフェースとは、オブジェクトの役割を表したものである。

この本屋さんでは、将来、CDやDVDも置きたくなった場合に備えて、「売物」や「荷物」といったインターフェースを導入しました。しかし、本だけしか置いていなければ、これらのインターフェースが無駄になる、という訳ではありません。

本屋さんのシステムでは、「本」オブジェクトは、「会計システム」に対しては「売物」の、「宅配システム」に対しては「荷物」の役割を果たします。インターフェースを作ったことで、これが設計書の上だけでなく、ソースコードでも表現されています。

究極のインターフェース指向設計とは

オブジェクト図では、役割名(ロール名)を記述せずに、オブジェクトどうしをリンク(クラス図では関連)の線で結ぶだけのこともあります。ですが、記述が省略されている場合でも、オブジェクトは何らかの役割を果たしています。

役割名(ロール名)を省略しなかったら、どうなるでしょうか。さきほど述べたように、オブジェクトの役割は、インターフェースとして表現されます。ですので、オブジェクトどうしのリンク(または、クラスどうしの関連)の数だけ、インターフェースができることになります。

引数を持つメソッドは、オブジェクトどうしのリンク(または、クラスどうしの関連)を作ります。例えば、

void ClassA::functionX ( ClassB* objectB );
        

というメソッドは、下記の図の通りにオブジェクトを結び付けます。

ClassAとClassBの関係を表すオブジェクト図

ここで、引数として渡されるobjectBオブジェクトは、functionXメソッドにおいては、ClassBとしてのすべてを発揮する訳ではありません。あくまで、functionXメソッドが求める役割を果たすだけです。

その役割を「functionXにおけるBの役割」という役割名(ロール名)で表し、インターフェースで表現すると、次のようになります。

ロール名を記述した、ClassAとClassBの関係を表すオブジェクト図

ClassAとClassBの関係を表すクラス図

そして、functionXメソッドの定義は、下記の通りになります。

void ClassA::functionX ( functionXにおけるBの役割* roleB );
        

この考え方を突き詰めると、究極的には、

すべてのメソッドの引数は、具体的なクラスではなく、抽象的なインターフェースにすべきである。

という結論になります。

Copyright(C) Seiichi Yoshida ( comet@aerith.net ). All rights reserved.