暗黙的なインターフェースの実装
2006年1月4日
JavaとC#はどちらも、純粋なインターフェース型の同じモデルを共有しています。interface Mailable
と宣言することで純粋なインターフェースを宣言でき、その後class Customer implements Mailable
(Javaの場合)でそれを実装することを宣言できます。クラスは任意の数の純粋なインターフェースを実装できます。このモデルが無視していることの1つは、クラスがあるときは常に暗黙的なインターフェースを持っているということです。
Customerのpublicな暗黙的インターフェースは、Customerで宣言されたすべてのpublicなメンバーです。(この暗黙的インターフェースは、私がこれまで見てきたすべてのOO言語に存在します。)JavaもC#も許していないことの1つは、暗黙的なインターフェースを実装することです。つまり、class ValuedCustomer implements Customer
と書くことはできません。
暗黙的なインターフェースを実装するということはどういう意味でしょうか?本質的には、ValuedCustomerクラスがCustomerのpublicインターフェースで宣言されたすべてのメソッドを実装しているが、その実装、つまりpublicメソッドの本体、非publicメソッドやデータは一切取得しないことを型システムに伝えることになります。言い換えれば、インターフェースの継承はあっても実装の継承はないということです。
それは、Customerを顧客のすべてのpublicメソッドを含むインターフェースに変更し、次にこのインターフェースを実装するCustomerImplクラスを持つことと同等です。
これがなぜ有用なのでしょうか?私が過去に覚えているケースの1つは、現在のコレクションフレームワーク以前のJavaの古い時代のことです。Vectorクラスを独自のものを実装したものに置き換えたかったのですが、Vectorはクラスであり、それをサブクラス化することしかできなかったため、できませんでした。ライブラリが自由な置換を可能にするインターフェースを提供していないと、このようなケースに時々遭遇します。この機能がないと、私たちは立ち往生してしまいます。
これは最近、テストで特に問題になります。何かをスタブアウトしたい場合がよくありますが、インターフェースがないと困難または不可能です。また、テストのための置換をサポートするためだけに純粋なインターフェース型を定義することにつながります。 InterfaceImplementationPairを使用することは一般的なアプローチですが、私たちの多くが好まないアプローチです。暗黙的なインターフェースの実装は、はるかにクリーンなアプローチになります。
では、なぜ言語はこれを許容しないのでしょうか?私は実際には知りません。しかし、私は言語設計者ではありません。かつて、この件についてアンダース・ヘルスバーグに質問する機会がありましたが、彼の答えは、メンバーを明示的に仮想として宣言する場合にのみオーバーライドを許可するという彼の好みと同じようなものでした。基本的に、それはサブクラス(またはこの場合は実装者)がスーパークラスを壊すことについての懸念であり、サブクラス化をどのように使用するかというはるかに広範なトピックに触れています。しかし、これは夕食時の短い会話だったので、議論を本当に徹底的に検討したとは確信していません。
これを書いた後、私の昔の同僚であるマイク・レッティグが、この問題の1つは、クラスが実際には複数の暗黙的インターフェースを定義していることだと指摘しました。たとえばJavaでは、顧客クラスは実際にはpublic、protected、package、privateの4つの暗黙的なインターフェースを定義しています。オブジェクトがCustomerと連携する場合、これらのインターフェースのいずれかを使用する可能性があります(Customerの別のインスタンスはprivate機能を使用できます)。暗黙的なインターフェースを実装したい場合、すべてを実装するか、どこまで実装するかを定義する必要があります。型システムがそれを追跡することがどれほど難しいかはわかりません。
もちろん、私が挙げた例はほとんどの場合、publicな暗黙的インターフェースだけで問題なく機能するため、実際にはそれで十分かもしれません。
イアン・グリフィスは、問題はクラスとインターフェースの混同にある可能性を指摘しました。マイクロソフトのCOMテクノロジーでは、この2つが明確に分離されていました。「COMでオブジェクトを使用したい場合は、インターフェースを介して行う必要があります。したがって、常に独自の実装を作成できます。」これは、COMインターフェースがバックグラウンドで生成されるVB6では非常に透明にされました。
この問題は、動的型付け言語では発生しません。別のクラスのインターフェースを実装したい場合は、同じメソッドを実装し、必要な場所でオブジェクトを使用するだけで済みます。すべてのメソッドを実装する必要はなく、特定のインタラクションで使用されているメソッドのみを実装する必要があります。これはテストに非常にうまく機能するスキームです。スモールトーカーはしばらくの間、これをImposterパターンと呼んでいました。また、Javaでこの種のことを行うために動的プロキシを使用することも非常に一般的ですが、暗黙的なインターフェースの実装の方がより伝達力が高いと感じています。
これらに意味はあるのでしょうか?ほとんどの場合、これはテストの問題であるように思えます。再実装を使用できない場合、テストダブルを挿入するのが非常に難しく、スーパークラスがデータベースへの実際の接続を必要とする場合、サブクラス化ではうまくいかないことがよくあります。これはテストの問題である可能性が高いでしょう。ロバート・コンリーは、テストにはVB6の再実装機能を使うことが多かったが、本番コードでは必要性を感じたことはないと話してくれました。