私たちはいくつかの理由で物事を発明したと思います。手続き型プログラミングが私たちのニーズを満たしていないためにOOPが生まれました。Abstractのような他のOOP機能は私たちのニーズを満たしていないため、同じことがインターフェイスにも当てはまります。
インターフェースとは何か、できること、使い方について書かれた記事やガイドはたくさんありますが、インターフェースの作成の背後にある実際の哲学は何ですか?なぜインターフェイスが必要なのですか?
概念的には、インターフェースはコントラクトです。これは、このインターフェースを実装するものはすべて、これらの一連のことを行うことができるという言い方です。
言語が異なれば、インターフェイスで定義できるものも異なり、それらを定義する方法も異なりますが、その概念は残っています。
インターフェイスを使用すると、特定のタスクがどのように完了するかを気にする必要がなくなります。完了したことを確認することができます。
実装が異なることを許可し、コードが必要なものの最小のサブセットのみを定義できるようにすることで、コードを一般化できます。
おそらく、一連の数字を画面に書き込むメソッドを書きたいと思うでしょう。他の (多くの) 一般的に使用されるデータ構造のいずれかで、配列、セット、ツリーに対してそれを行うためのメソッドを書き回したくありません。配列を扱っているのか連結リストを扱っているのかを気にする必要はありません。項目のシーケンスを取得する何らかの方法が必要なだけです。インターフェイスを使用すると、必要なものの最小限のセット、たとえばgetNextItem
メソッドを定義できます。これらのデータ構造のすべてがそのメソッドとインターフェイスを実装する場合、1 つの一般化されたメソッドを使用できます。これは、使用するデータ構造のタイプごとに個別のメソッドを記述するよりもはるかに簡単です。(これはインターフェイスの唯一の使用方法ではなく、一般的な使用方法です。)
Javaでは、クラスは1つのクラスからのみ継承できますが、複数のインターフェースを実装できます。インターフェイスは抽象クラスに似ていますが、クラスが抽象クラスを拡張する場合、そのクラスは他のクラスを拡張できません。インターフェイスはその問題を解決します。クラスに抽象クラスを拡張させ、多くのインターフェイスを実装することができます。
IMOは、インターフェイスを説明する最高のテキストは、RobertMartinのISPです。
インターフェイスの真の力は、(1)オブジェクトを多くの異なるタイプがあるかのように扱うことができ(クラスは異なるインターフェイスを実装できるため)、(2)異なる階層ツリーのオブジェクトを同じものであるかのように扱うことができるという事実に由来しますタイプ(関連しないクラスが同じインターフェースを実装できるため)。
何らかのインターフェイスタイプ(Comparableなど)のパラメータを持つメソッドがある場合、このメソッドは、クラスを「無視する」インターフェイスを実装する任意のオブジェクト(たとえば、StringまたはInteger、2つの無関係なクラス)を受け入れることができることを意味します。 Comparableを実装します)。
したがって、インターフェースは抽象クラスよりもはるかに強力な抽象化です。
私は susomena に完全に同意しますが、インターフェイスを使用するときに得られる利点はそれだけではありません。
例えば。現在のアプリケーションでは、単体テストに関してモッキングが重要な役割を果たしています。単体テストの哲学は、この単体のコードを実際にテストするだけでよいということです。ただし、場合によっては、他の依存関係があり、「テスト対象のユニット」(SUT) を取得する必要があります。そして、この依存関係には他の依存関係などが含まれている可能性があります。したがって、依存関係ツリーを複雑に構築して構成する代わりに、この特定の依存関係を偽造するだけです。多くのモッキング フレームワークは、SUT が依存するクラスのインターフェイスを使用してセットアップする必要があります。通常、具体的なクラスをモックすることは可能ですが、私たちの場合、具体的なクラスをモックすると、コンストラクターの呼び出しが原因で、単体テストの奇妙な動作が発生しました。しかし、インターフェイスにはコンストラクターがないため、インターフェイスのモックはそうではありませんでした。
抽象クラスの実装を選択するという私の個人的な哲学は、抽象基本クラスのデフォルトの動作が必要な階層クラス構造を構築することです。デフォルトの動作がない場合、派生クラスは継承する必要があります。抽象クラスの実装よりもインターフェイスを選択しないという点はわかりません。
そして、ここで、別の手法よりも 1 つを選択する方法の別の (あまり良くない) 例を示します。Cat
やのような動物クラスがたくさんあると想像してくださいDog
。抽象クラスは、次のAnimal
デフォルト メソッドを実装する場合があります。
public abstract void Feed()
{
Console.WriteLine("Feeding with meat");
}
動物がたくさんいる場合は、肉があれば大丈夫です。肉が嫌いな少量の動物の場合は、 の新しい動作を再実装するだけで済みFeed()
ます。
しかし、動物がちょっとした美食家だったら?そして要件は、すべての動物が好みの食べ物を得るということでしたか? そこでインターフェースを選択したいので、プログラマーはFeed()
のすべてのタイプに対してメソッドを実装する必要がありIAnimal
ます。
インターフェイスが OOP に持ち込まれたのは、the producer consumer paradigm
. 例を挙げて説明しましょう...
すべての大手自動車会社にタイヤを供給するベンダーがあるとします。自動車会社はCONSUMERと見なされ、タイヤ ベンダーはPRODUCERと見なされます。消費者は、タイヤを製造する際のさまざまな仕様 (直径、ホイール ベースなど) を製造業者に指示します。また、プロデューサーはこれらすべての仕様に厳密に従う必要があります。
これから OOP との類似性を考えてみましょう... UI を開発しているスタックを実装するアプリケーションを開発しましょう。また、実際にスタックを実装するためにスタック ライブラリ (.dll または .class として) を使用していると仮定します。ここでは、 あなたが消費者であり、実際にスタック プログラムを作成した人が生産者です。ここで、スタックのさまざまな仕様を指定して、要素をプッシュして要素をポップするための規定と、のぞき見するための規定が必要であることを示します。現在のスタック ポインターで。また、戻り値の型とパラメーター (関数のプロトタイプ) を指定して、これらの規定にアクセスするためのインターフェイスを指定し、アプリケーションでそれらを使用する方法を理解できるようにします。
これを実現する最も簡単な方法は、インターフェースを作成し、プロデューサーにこのインターフェースを実装するよう依頼することです。そのため、プロデューサーが使用するロジックに関係なく(ニーズが何らかの方法で満たされている限り、実装について気にする必要はありません)、正確な戻り値の型とパラメーターを使用して、push、pop、および peep メソッドを実装します。
言い換えれば、プロデューサーにインターフェースを実装させることで、仕様とニーズにアクセスする方法をプロデューサーに厳密に守らせることができます。ベンダーがあなたのインターフェイスを実装していない場合、どのベンダーのスタックも受け入れられません。それがあなたの正確なニーズに合っているかどうか確信が持てないからです。
class CStack implements StackInterface
{//this class produced by the producer must have all three method implementation
//interface defined by the consumer as per his needs
bool push(int a){
...
}
int pop(){
....
}
int peep(){
...
}
}