インターフェイスを使用すると、静的に型指定された言語でポリモーフィズムをサポートできます。オブジェクト指向の純粋主義者は、完全な機能を備えたオブジェクト指向言語であるためには、言語が継承、カプセル化、モジュール性、およびポリモーフィズムを提供する必要があると主張します。動的型付け(またはダック型付け)の言語(Smalltalkなど)では、ポリモーフィズムは簡単です。ただし、静的に型付けされた言語(JavaやC#など)では、ポリモーフィズムは些細なことではありません(実際、表面的には強い型付けの概念と対立しているようです)。
実演させてください:
動的型付け(またはダック型付け)言語(Smalltalkなど)では、すべての変数はオブジェクトへの参照です(それ以下でもそれ以上でもありません)。したがって、Smalltalkでは次のことができます。
|anAnimal|
anAnimal := Pig new.
anAnimal makeNoise.
anAnimal := Cow new.
anAnimal makeNoise.
そのコード:
- anAnimalと呼ばれるローカル変数を宣言します(変数のTYPEは指定しないことに注意してください。すべての変数はオブジェクトへの参照であり、それ以上でもそれ以下でもありません)。
- 「Pig」という名前のクラスの新しいインスタンスを作成します
- Pigの新しいインスタンスを変数anAnimalに割り当てます。
makeNoise
豚にメッセージを送信します。
- 牛を使用してすべてを繰り返しますが、豚とまったく同じ変数に割り当てます。
同じJavaコードは次のようになります(DuckとCowがAnimalのサブクラスであると仮定します。
Animal anAnimal = new Pig();
duck.makeNoise();
anAnimal = new Cow();
cow.makeNoise();
クラスVegetableを紹介するまでは、これで問題ありません。野菜は動物と同じ行動をしますが、すべてではありません。たとえば、動物と野菜の両方が成長できる可能性がありますが、明らかに野菜は音を立てず、動物を収穫することはできません。
Smalltalkでは、次のように書くことができます。
|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.
aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.
これはSmalltalkで完全に機能します。これは、アヒルタイプであるためです(アヒルのように歩き、アヒルのように鳴く場合は、アヒルです)。この場合、メッセージがオブジェクトに送信されると、ルックアップが実行されます。受信者のメソッドリスト。一致するメソッドが見つかった場合は、それが呼び出されます。そうでない場合は、ある種のNoSuchMethodError例外がスローされますが、すべて実行時に実行されます。
しかし、静的に型付けされた言語であるJavaでは、変数にどの型を割り当てることができますか?トウモロコシは成長をサポートするために野菜から継承する必要がありますが、ノイズを発生させないため、動物から継承することはできません。牛はmakeNoiseをサポートするために動物から継承する必要がありますが、収穫を実装する必要がないため、野菜から継承することはできません。多重継承、つまり複数のクラスから継承する機能が必要なようです。しかし、ポップアップするすべてのエッジケースがあるため、これはかなり難しい言語機能であることがわかります(複数の並列スーパークラスが同じメソッドを実装するとどうなりますか?など)。
インターフェースに沿って...
動物と野菜のクラスを作成し、それぞれがGrowableを実装すると、牛は動物であり、トウモロコシは野菜であると宣言できます。動物と野菜の両方が成長可能であると宣言することもできます。それは私たちがすべてを成長させるためにこれを書くことを可能にします:
List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());
for(Growable g : list) {
g.grow();
}
そして、それは私たちにこれをさせて、動物の音を立てます:
List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
a.makeNoise();
}
ダックタイピング言語の利点は、非常に優れたポリモーフィズムが得られることです。動作を提供するためにクラスが行う必要があるのは、メソッドを提供することだけです。誰もが上手にプレイし、定義されたメソッドに一致するメッセージのみを送信する限り、すべてが良好です。欠点は、以下の種類のエラーが実行時までキャッチされないことです。
|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.
静的に型付けされた言語は、コンパイル時に以下の2種類のエラーをキャッチするため、はるかに優れた「コントラクトによるプログラミング」を提供します。
// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();
farmObject makeNoise();
-
// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest();
だから....要約すると:
インターフェイスの実装では、オブジェクトが実行できることの種類(相互作用)を指定でき、クラスの継承では、実行方法(実装)を指定できます。
インターフェイスは、コンパイラの型チェックを犠牲にすることなく、「真の」ポリモーフィズムの多くの利点を提供します。