私はしばらくの間、C#で勉強してコーディングしています。しかし、それでもインターフェイスの有用性を理解することはできません。彼らはあまりテーブルに持ってきません。関数のシグネチャを提供する以外は何もしません。実装する必要のある関数の名前とシグネチャを覚えていれば、その必要はありません。それらは、(インターフェース内の)上記の関数が継承クラスに実装されていることを確認するためだけにあります。
C#は優れた言語ですが、場合によっては、最初にMicrosoftが問題を作成し(多重継承を許可しない)、次に解決策を提供するという感じがすることがあります。
それは、限られたコーディング経験に基づく私の理解です。インターフェースについてどう思いますか?どのくらいの頻度でそれらを使用し、何を使用するのですか?
これらは、(インターフェース内の)前述の関数が継承クラスに実装されていることを確認するためだけにあります。
正しい。これは、機能を正当化するのに十分なメリットです。他の人が言ったように、インターフェースは特定のメソッド、プロパティ、イベントを実装するための契約上の義務です。静的に型付けされた言語の魅力的な利点は、コードが依存するコントラクトが実際に満たされていることをコンパイラーが検証できることです。
とはいえ、インターフェースは契約上の義務を表現するのにかなり弱い方法です。契約上の義務を表現するためのより強力で柔軟な方法が必要な場合は、Visual Studioの最新バージョンに付属するコード契約機能を調べてください。
C#は優れた言語ですが、場合によっては、最初にMicrosoftが問題を作成し(多重継承を許可しない)、次に解決策を提供するという感じがすることがあります。
まあ私はあなたがそれを気に入ってうれしいです。
すべての複雑なソフトウェア設計は、競合する機能を互いに比較検討し、小さなコストで大きなメリットをもたらす「スイートスポット」を見つけようとした結果です。多重継承を許可する言語実装の共有の目的では、利点が比較的少なく、コストが比較的大きいことを苦痛な経験を通して学びました。実装の詳細を共有しないインターフェースでのみ多重継承を許可すると、ほとんどのコストなしで多重継承の多くの利点が得られます。
したがって、この例では、PowerSocketは他のオブジェクトについて他に何も認識していません。オブジェクトはすべて、PowerSocketによって提供されるPowerに依存するため、IPowerPlugを実装し、そうすることで、それに接続できます。
インターフェースは、オブジェクトが互いに他のことを何も知る必要なく連携して動作するために使用できるコントラクトを提供するので便利です。
関数のシグネチャを提供する以外は何もしません。実装する必要がある関数の名前とシグネチャを覚えていれば、それらは必要ありません。
インターフェースの要点は、実装するメソッドを覚えやすくすることではなく、ここでコントラクトを定義することです。 foreach P.Brian.Mackeyの例 (これは 間違っていることが判明 ですが、気にしません)では、IEnumerableはの間のコントラクトを定義しますforeachおよびany列挙可能なもの。それは言う:「あなたが契約に固執する限り(IEnumerableを実装する限り)、すべての要素を繰り返し処理することを約束します」。そして、それは素晴らしいことです(非動的言語の場合)。
インターフェイスのおかげで、2つのクラス間の結合を非常に低くすることができます。
インターフェイスは、適切に分離された構造を維持するための最良の方法です。
テストを作成すると、具象クラスがテスト環境で機能しないことがわかります。
例:Data Access Serviceクラスに依存するクラスをテストするとします。そのクラスがWebサービスまたはデータベースと通信している場合、単体テストはテスト環境では実行されません(さらに、統合テストに変わります)。
解決? Data Access ServiceおよびMockにInterfaceを使用して、クラスをテストできますユニットとして。
一方、バインドに関しては、WPFとSilverlightはインターフェイスでまったく機能しません。これはかなり厄介なしわです。
インターフェイスは(静的)多態性のバックボーンです!重要なのはインターフェースです。サブクラスは基本的に、すでに実装されている親のインターフェースを継承するため、継承はインターフェースなしでは機能しません。
どのくらいの頻度でそれらを使用し、何を使用するのですか?
かなり頻繁に。プラグ可能である必要があるすべては私のアプリケーションのインターフェイスです。多くの場合、同じ振る舞いを提供する必要がある他の無関係なクラスがあります。このような問題は継承では解決できません。
同じデータに対して操作を実行するために異なるアルゴリズムが必要ですか?インターフェイスを使用してください( 戦略パターンを参照 )!
別のリスト実装を使用しますか?インターフェースに対するコードと呼び出し側は実装について心配する必要はありません!
1つの理由から、古くなったインターフェイスに対してコードを作成することは(OOPだけでなく)良い習慣と考えられています。ニーズに合わないとわかったときに実装を変更するのは簡単です。多重継承のみでそれを実現しようとすると、または必要なインターフェイスを提供するために空のクラスを作成することになると、かなり面倒です。
おそらくforeach
を使用していて、それが非常に便利な反復ツールであることがわかりました。 IEnumerable を機能させるにはインターフェースが必要であることをご存知ですか?
それは確かにインターフェースの有用性を語る具体的なケースです。
インターフェイスは、プラグが家庭用配線にあるようにオブジェクトをコーディングするためのものです。ラジオを家の配線に直接はんだ付けしますか?あなたの掃除機はどうですか?もちろん違います。プラグとプラグが差し込まれるコンセントは、家の配線と、プラグからの電力を必要とするデバイスとの間の「インターフェース」を形成します。家の配線は、3極の接地プラグを使用し、120 VAC <= 15 Aの電力を必要とすることを除いて、デバイスについて何も知る必要はありません。逆に、このデバイスは、120 VAC <= 15Aを提供する便利な場所に1つまたは複数の3ピンコンセントが配置されていること以外は、家の配線方法に関する難解な知識を必要としません。
インターフェイスはコードで非常によく似た機能を実行します。オブジェクトは、特定の変数、パラメーター、または戻り値の型がインターフェイス型であることを宣言できます。インターフェイスをnew
キーワードを使用して直接インスタンス化することはできませんが、オブジェクトに、インターフェイスの実装を指定または検索して、操作する必要があります。オブジェクトに依存関係があると、その依存関係を正確に知る必要はありません。依存関係に対してメソッドX、Y、Zを呼び出すことができることを知っていれば十分です。インターフェースの実装は、それらがどのように使用されるかを知る必要はなく、特定のシグニチャーを備えたメソッドX、Y、およびZを提供することが期待されることを知っている必要があります。
したがって、同じインターフェースの背後にある複数のオブジェクトを抽象化することにより、そのインターフェースのオブジェクトのコンシューマーに共通の機能セットを提供します。たとえば、オブジェクトがリスト、ディクショナリ、LinkedList、OrderedListなどであることを知っている必要はありません。これらすべてがIEnumerableであることを知っているので、IEnumerableのメソッドを使用して、これらのコレクションの各要素を一度に1つずつ調べることができます。出力クラスがConsoleWriter、FileWriter、NetworkStreamWriter、または他のタイプのライターをとるMulticastWriterであることを知っている必要はありません。あなたが知る必要があるのは、それらがすべてIWriter(または何でも)であること、したがって文字列を渡すことができる「書き込み」メソッドがあり、その文字列が出力されることです。
プログラマーが(少なくとも)多重継承を持つことは明らかに扱いですが、これはほとんど取るに足らない省略であり、(ほとんどの場合)多重継承に依存すべきではありません。これの理由は複雑ですが、本当にそれについて学びたい場合は、2つの最も有名な( TIOBEインデックス による)プログラミングの経験を検討してくださいそれをサポートする言語:C++とPython(3番目と8番目のそれぞれ))。
Pythonでは、多重継承がサポートされていますが、プログラマーはほとんどの場合誤解し、その仕組みを理解していると述べます。つまり、トピックに関するこのペーパーを読んで理解することです メソッド解決順序 。 Pythonで起こった別のことは、その言語へのインターフェースのようなものです-Zope.Interfaces。
C++の場合は、「ダイヤモンド階層C++」をgoogleにして、これから説明する醜さを見てください。 C++プロは多重継承の使い方を知っています。他の誰もが通常、結果がどうなるかわからないまま遊んでいます。インターフェースの有用性を示すもう1つのことは、多くの場合、クラスがその親の動作を完全にオーバーライドする必要がある場合があることです。このような場合、親の実装は不要であり、子クラスに親のプライベート変数のメモリを負荷するだけです。これは、C#の時代には関係ないかもしれませんが、組み込みプログラミングを行うときに問題になります。インターフェイスを使用する場合、その問題は存在しません。
結論として、インターフェースは契約を実施するため、私の意見ではOOPの重要な部分です。多重継承は、限られた場合に役立ちますが、通常、その使い方を知っている人にのみ役立ちます。だから、あなたが初心者であれば、多重継承の欠如によって扱われる人です-これはあなたに間違いを犯さないより良い機会を与えます。
また、歴史的に、インターフェイスのアイデアは、MicrosoftのC#設計仕様よりもはるかに早く根付いていました。ほとんどの人はC#をJava(ほとんどの意味で)からのアップグレード)と見なしており、C#がインターフェイスを取得した場所を推測しています-Java。プロトコルは同じ概念の古いWordであり、それは方法です.NETより古い。
更新:これで、別の質問に答えたかもしれません-なぜインターフェースが多重継承ではなく、なぜこれがあなたが探していた答えのように見えたのでしょうか。 OO言語に加えて、言語には2つのうち少なくとも1つが必要であり、他の回答は元の質問をカバーしています。
インターフェイスを使用せずにクリーンでオブジェクト指向のC#コードを想像するのは難しいです。クラスに特定の基本クラスからの継承を強制することなく特定の機能の可用性を強制したい場合はいつでもそれらを使用します。これにより、コードに適切なレベルの(低)結合を持たせることができます。
多重継承にはそれ自身の苦痛が伴うと主張する前に、多重継承がインターフェースを持つよりも優れていることに私は同意しません。インターフェイスは、ポリモーフィズムとコードの再利用を可能にするための基本的なツールですが、他に何が必要ですか?
私はそれに関係していると言えるでしょう。最初にOOおよびC#について学習し始めたとき、私もインターフェイスを取得していませんでした。問題はありません。インターフェイスの便利さを理解できるようなものに出会う必要があります。
2つの方法を試してみましょう。そして、一般化してください。
1つ試す
あなたは英語を母国語とするとします。あなたは英語が母国語ではない別の国に行きます。あなたは助けが必要です。あなたを助けることができる誰かが必要です。
「ねえ、あなたはアメリカで生まれましたか?」これが継承です。
または、「ねえ、あなたは英語を話しますか?」これがインターフェースです。
それが何をするか気になっているなら、あなたはインターフェースを信頼することができます。あなたが何であるかを気にするなら、あなたは継承に依存しています。
継承に依存しても大丈夫です。英語を話し、お茶が好きで、サッカーが好きな人が必要な場合は、英国人を頼むほうがよいでしょう。 :)
2つ試す
では、別の例を試してみましょう。
さまざまなデータベースを使用していて、それらを扱うために抽象クラスを実装する必要があります。クラスをDBベンダーからのクラスに渡します。
public abstract class SuperDatabaseHelper
{
void Connect (string User, string Password)
}
public abstract class HiperDatabaseHelper
{
void Connect (string Password, string User)
}
多重継承だと思いますか?上記のケースでそれを試してください。できません。コンパイラーは、呼び出そうとしているConnectメソッドを認識しません。
interface ISuperDatabaseHelper
{
void Connect (string User, string Password)
}
interface IHiperDatabaseHelper
{
void Connect (string Password, string User)
}
ここで、少なくともC#で、インターフェイスを明示的に実装できる、操作できるものがあります。
public class MyDatabaseHelper : ISuperDatabaseHelper, IHiperDatabaseHelper
{
IHiperDataBaseHelper.Connect(string Password, string User)
{
//
}
ISuperDataBaseHelper.Connect(string User, string Password)
{
//
}
}
結論
例は最高ではありませんが、私はそれが全体的に意味をなすと思います。
インターフェイスの必要性を感じた場合にのみ、インターフェイスを「取得」します。彼らができるまで、あなたは彼らがあなたのためではないと思うでしょう。
私は個人的に抽象クラスが大好きで、インターフェースよりもそれを使用しています。主な違いは、IDisposable、IEnumerableなどの.NETインターフェイスとの統合、およびCOM相互運用機能との統合です。また、インターフェースは、抽象クラスよりも書くのに少し手間がかからず、クラスは複数のインターフェースを実装できますが、1つのクラスからしか継承できません。
とは言っても、インターフェイスを使用するmostのものは、抽象クラスの方が適していることがわかりました。純粋な仮想関数(抽象関数)を使用すると、インターフェイスが実装者にそのメンバーのすべてを定義させる方法と同様に、実装者に関数を定義させることができます。
ただし、スーパークラスに特定のデザインを課したくない場合は、通常、インターフェイスを使用しますが、抽象クラスを使用して、ほとんどが既に実装されている再利用可能なデザインを使用します。
System.ComponentModel名前空間を使用してプラグイン環境を作成することで、インターフェイスを広範囲に使用しました。彼らはかなり重宝します。
主な理由は2つあります。
インターフェース自体はあまり役に立ちません。しかし、具象クラスで実装すると、1つ以上の実装を柔軟に行えることがわかります。ボーナスは、インターフェイスを使用するオブジェクトが実際の実装の詳細がどのように行われるかを知る必要がないことです。これはカプセル化と呼ばれます。
インターフェースを使用すると、システムの分離を維持できるため、リファクタリング、変更、および再デプロイが容易になります。これはオブジェクト指向の正統性にとって非常に中心的な概念であり、C++の達人がインターフェイスとまったく同等の「純粋な抽象クラス」を作成したときに私は最初にそれについて学びました。
若いプログラマー/開発者は、C#を学習するだけでインターフェイスの有用性を理解できない場合があります。クラスを使用してコードを記述し、コードが正常に機能するためですが、実際のシナリオでは、スケーラブルで堅牢で保守可能なアプリケーションの構築に一部のアーキテクチャとパターンは、インターフェイスを使用することによってのみ可能になります。たとえば、依存関係の注入です。
それらは主にコードの再利用性のために使用されます。インターフェイスにコーディングする場合、そのインターフェイスから継承する別のクラスを使用して、すべてを壊すことはできません。
また、クライアントがクラスを実行できるようにクライアントに通知したいが、実際のコードを提供したくないWebサービスでは非常に便利です。
実際の実装:
オブジェクトをインターフェイスタイプとしてキャストできます。
IHelper h = (IHelper)o;
h.HelperMethod();
インターフェースのリストを作成できます
List<IHelper> HelperList = new List<IHelper>();
これらのオブジェクトを使用すると、任意のインターフェイスメソッドまたはプロパティにアクセスできます。このようにして、プログラムの一部にインターフェースを定義できます。そして、その周りにロジックを構築します。次に、他の誰かが自分のビジネスオブジェクトにインターフェイスを実装できます。 BOが変更された場合、インターフェースコンポーネントのロジックを変更できるため、ピースのロジックを変更する必要はありません。
以下は疑似コードです。
class MyClass{
private MyInterface = new MyInterfaceImplementationB();
// Code using Thingy
}
interface MyInterface{
myMethod();
}
class MyInterfaceImplementationA{ myMethod(){ // method implementation A } }
class MyInterfaceImplementationB{ myMethod(){ // method implementation B } }
class MyInterfaceImplementationC{ myMethod(){ // method implementation C } }
最後のクラスは、完全に異なる実装にすることができます。
多重継承が可能でない限り、継承は親クラスの実装を強制し、物事をより厳格にします。一方、インターフェースに対するプログラミングでは、コードまたはフレームワークを非常に柔軟にすることができます。継承チェーンでクラスを入れ替えたいと思った場合に遭遇したことがあれば、その理由を理解できます。
たとえば、元々ディスクからデータを読み取ることを目的としたリーダーを提供するフレームワークは、まったく同じ方法でまったく異なる方法で再実装できます。たとえば、モールス信号を解釈するように。
インターフェースは、システムが配信する特定の種類のメッセージをクラスが理解(およびサブスクライブ)するためのメカニズムを提供することにより、プラグインスタイルのモジュール性に役立ちます。詳しく説明します。
アプリケーションでは、フォームがロードまたは再ロードされるたびに、フォームがホストしているすべてのものをクリアすることを決定します。 IClear
を実装するClear
インターフェースを定義します。さらに、ユーザーが保存ボタンを押すたびに、フォームがその状態を保持しようとすることを決定します。したがって、ISave
を順守するすべてのものは、その状態を維持するためのメッセージを受け取ります。もちろん、実際には、ほとんどのインターフェースはいくつかのメッセージを処理します。
インターフェイスを際立たせるのは、継承なしで共通の動作を実現できることです。特定のインターフェイスを実装するクラスは、コマンドが発行されたときの動作方法(コマンドメッセージ)またはクエリされたときの応答方法(クエリメッセージ)を単純に理解します。基本的に、アプリケーションのクラスは、アプリケーションが提供するメッセージを理解します。これにより、プラグイン可能なモジュラーシステムを簡単に構築できます。
ほとんどの言語では、インターフェイスに準拠しているものをクエリするためのメカニズム( [〜#〜] linq [〜#〜] など)があります。これは通常、条件付きロジックを排除するのに役立ちます(同じ継承チェーンから派生する必要のないもの)に、(特定のメッセージに従って)同様に動作する方法を伝える必要がないためです。代わりに、特定のメッセージを理解するすべてのものを収集し(インターフェースに従う)、メッセージを公開します。
たとえば、次のように置き換えることができます...
Me.PublishDate.Clear()
Me.Subject.Clear()
Me.Body.Clear()
...と:
For Each ctl As IClear In Me.Controls.OfType(Of IClear)()
ctl.Clear()
Next
これは効果的に次のように聞こえます:
聞いて、聞いて!清算を理解している皆さん、今すぐ
Clear
してください。
このようにして、プログラムによって、すべてをクリアするように指示することを回避できます。また、クリア可能なアイテムが将来追加されると、追加のコードなしで応答します。