C#で拡張メソッドを持つインターフェイスの代わりに抽象クラスを使用する場合


70

「抽象クラス」と「インターフェース」は類似した概念であり、インターフェースはより抽象的な概念です。1つの差別化要因は、抽象クラスが必要なときに派生クラスのメソッド実装を提供することです。ただし、C#では、この差別化要因は、インターフェイスメソッドの実装を提供できる拡張メソッドの最近の導入によって削減されました。別の差別化要因は、クラスが1つの抽象クラスのみを継承できる(つまり、多重継承がない)が、複数のインターフェイスを実装できることです。これにより、インターフェイスの制限が緩和され、柔軟性が高まります。それでは、C#では、拡張メソッドを備えたインターフェイスの代わりに抽象クラスをいつ使用すべきでしょうか?

インターフェイス+拡張メソッドモデルの注目すべき例はLINQです。ここではIEnumerable、多数の拡張メソッドを介して実装されるすべてのタイプに対してクエリ機能が提供されます。


2
また、インターフェイスでも「プロパティ」を使用できます。
グルシャン

1
一般的なOODの質問ではなく、C#固有のもののように聞こえます。(他のほとんどのOO言語には拡張メソッドもプロパティもありません。)
PéterTörök11年


4
拡張メソッドは、抽象クラスが解決する問題をまったく解決しないため、その理由は、Javaまたは他の同様の単一継承言語の場合と同じです。
ジョブ

3
抽象クラスメソッドの実装の必要性は、拡張メソッドによって削減されていません。拡張メソッドはプライベートメンバーフィールドにアクセスできません。また、それらを仮想として宣言したり、派生クラスでオーバーライドしたりすることもできません。
zooone9243

回答:


63

クラスの一部を実装する必要がある場合。私が使用した最良の例は、テンプレートメソッドパターンです。

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

したがって、Do()が呼び出されたときに実行されるステップを、それらの実装方法の詳細を知らなくても定義できます。派生クラスは抽象メソッドを実装する必要がありますが、Do()メソッドは実装しないでください。

拡張メソッドは、必ずしも式の「クラスの一部である必要があります」部分を満たしているわけではありません。さらに、iirc、拡張メソッドはスコープ内でpublic以外のものにはなりません(表示されません)。

編集する

この質問は、私が最初にクレジットを与えたよりも興味深いものです。さらに調査すると、Jon Skeet SOでこのような質問に答え、インターフェイス+拡張メソッドを使用することに賛成しました。また、潜在的な欠点は、この方法で設計されたオブジェクト階層に対するリフレクションを使用していることです。

個人的には、現在一般的な慣行を変更することの利点を理解するのに苦労していますが、それを行う上でのデメリットはほとんどありません。

ユーティリティクラスを介して多くの言語でこの方法でプログラムすることが可能であることに注意する必要があります。拡張機能は、メソッドをクラスに属しているように見せるための構文糖を提供するだけです。


それに私を打つ
.--ジョルジ

1
しかし、インターフェイスと拡張メソッドでも同じことができます。そこで、抽象基本クラスDo()で定義しているメソッドは、拡張メソッドとして定義されます。そして、派生クラスの定義用に残されたメソッドDoThis()は、メインインターフェイスのメンバーになります。インターフェイスを使用すると、複数の実装/継承をサポートできます。これを参照してください-odetocode.com/blogs/scott/archive/2009/10/05/…–
グルシャン

@Gulshan:物事を複数の方法で実行できるため、選択した方法が無効になるわけではありません。インターフェイスを使用する利点はありません。抽象クラス自体インターフェースです。複数の実装をサポートするためのインターフェースは必要ありません。
ThomasX

また、インターフェース+拡張モデルには欠点がある場合があります。たとえば、Linqライブラリを取り上げます。それは素晴らしいことですが、そのコードファイルで一度だけ使用する必要がある場合、コードファイルのすべてのIEnumerableでIntelliSenseで完全なLinqライブラリを利用できます。これにより、IDEのパフォーマンスが大幅に低下する可能性があります。
キース

-1抽象クラスがインターフェイスよりもテンプレートメソッドに適している方法を実証していないため
Benjamin Hodgson

25

それはあなたがモデル化したいものについてのすべてです:

抽象クラスは継承によって機能します。特別な基本クラスであるため、is-a -relationship をモデル化しています。

たとえば、犬動物であるため、

class Dog : Animal { ... }

一般的な全動物を実際に作成するのは理にかなっていないため(どのように見えますか?)、このAnimal基本クラスを作成しますabstractが、それでも基本クラスです。そして、Dogクラスはなしで意味をなしませんAnimal

一方、インターフェースは別の話です。それらは継承を使用しませんが、多相性を提供します(これ継承で実装できます)。彼らはis-a関係をモデル化していませんが、それ以上はサポートしています。

たとえば、別のIComparableオブジェクトとの比較をサポートするオブジェクトを使用ます。

クラスの機能は、実装するインターフェースに依存ません。インターフェースは、機能にアクセスする一般的な方法を提供するだけです。私たちはまだそれらを実装させることなくDisposea Graphicsまたはaの可能性がありFileStreamますIDisposableインタフェースだけに関する方法を一緒。

原則として、クラスの動作を変更せずに、拡張機能と同様にインターフェイスを追加および削除して、クラスへのアクセスを強化することができます。ただし、オブジェクトが無意味になるため、基本クラスを取り去ることはできません!


基本クラスの抽象を保持することを選択するのはいつですか?
グルシャン

1
@Gulshan:もしそうなら-何らかの理由で-基本クラスのインスタンスを実際に作成しても意味がありません。チワワまたはホオジロザメを「作成」することはできますがさらに専門化することなく動物を作成することはできませんAnimal。したがって、抽象化することになります。これによりabstract、派生クラスに特化した関数を作成できます。以上のプログラミング用語に-それはおそらく、作成する意味をなさないんUserControlが、ようButton であるUserControlので、我々は、クラス/基底クラス関係の同じ種類を持っています。
ダリオ

抽象クラスがある場合よりも抽象メソッドがある場合。そして、それらを実装することに価値がないときに持っている抽象メソッド(動物の「go」など)。そしてもちろん、抽象クラスにするよりも、一般クラスのオブジェクトを許可したくない場合があります。
ダイニウス

「BはAです」と言うことが文法的および意味的に意味がある場合、Aは抽象クラスでなければならないというルールはありません。本当にクラスを回避できなくなるまで、インターフェイスを優先する必要があります。コードの共有は、インターフェースの構成などを介して行うことができます。これにより、奇妙な継承ツリーで隅に自分自身をペイントすることを簡単に回避できます。
サラ

3

何年も抽象クラスを使用していない(少なくとも作成されていない)ことに気付きました。

抽象クラスは、インターフェイスと実装の間のやや奇妙な獣です。抽象クラスには、私のクモ感覚を刺すようなものがあります。私は、インターフェースの背後にある実装を何らかの方法で「公開」すべきではない、と主張します(または、提携します)。

インターフェイスを実装するクラス間で共通の動作が必要な場合でも、抽象クラスではなく、独立したヘルパークラスを使用します。

インターフェースに行きます。


2
-1:あなたが何歳かはわかりませんが、デザインパターン、特にテンプレートメソッドパターンを学びたいかもしれません。デザインパターンは、あなたをより優れたプログラマにし、抽象クラスの無知を終わらせます。
ジムG.

チクチクする感覚についても同じです。C#とJavaの最も重要な機能であるクラスは、型を作成し、すぐに1つの実装に永久にチェーンする機能です。
ジェシーミリカン

2

私見、私はここに概念の混合があると思う。

クラスは特定の種類の継承を使用し、インターフェイスは異なる種類の継承を使用します。

両方の種類の継承は重要かつ有用であり、それぞれに長所と短所があります。

最初から始めましょう。

インターフェースに関する話は、C ++からさかのぼって始まります。1980年代半ば、C ++が開発されたとき、異なる種類のインターフェイスとしてのインターフェイスのアイデアはまだ実現していませんでした(やがて実現します)。

C ++には、クラスが使用する継承の一種である、実装継承と呼ばれる1種類の継承しかありませんでした。

GoF Bookの推奨事項「実装用ではなく、インターフェイス用にプログラムする」を適用できるようにするため、C ++は抽象クラスと複数の実装の継承をサポートし、C#のインターフェイスでできること(そして役立つこと!) 。

C#では、新しい種類のインターフェイス、新しい種類の継承、インターフェイス継承が導入され、「実装ではなくインターフェイス用にプログラミングする」をサポートするための少なくとも2つの異なる方法が提供されます。インターフェースの継承による多重継承をサポートします(実装の継承ではサポートしません)。

したがって、C#のインターフェイスの背後にあるアーキテクチャ上の理由は、GoFの推奨事項です。

これが助けになることを願っています。

よろしく、ガストン


1

インターフェースに拡張メソッドを適用すると、共通のインターフェースのみを共有する可能性のあるクラスに共通の動作を適用するのに役立ちます。そのようなクラスはすでに存在し、他の手段を介して拡張機能に閉じられる場合があります。

新しい設計では、インターフェイスで拡張メソッドを使用することをお勧めします。型の階層の場合、拡張メソッドは、変更のために階層全体を開かずに動作を拡張する手段を提供します。

ただし、拡張メソッドはプライベート実装にアクセスできないため、階層の最上部で、パブリックインターフェイスの背後にプライベート実装をカプセル化する必要がある場合、抽象クラスが唯一の方法になります。


いいえ、それが唯一の安全な方法ではありません。別の安全な方法があります。それが拡張メソッドです。クライアントはまったく感染しません。そのため、インターフェイスと拡張メソッドについて言及しました。
グルシャン

@Ed James私は、「より強力ではない」=「より柔軟」だと思います。より柔軟なインターフェースではなく、より強力な抽象クラスを選択するのはいつですか?
グルシャン

@Ed James asp.mvcでは、拡張メソッドが最初から使用されていました。あれについてどう思う?
グルシャン

0

C#では多重継承はサポートされていないと思うので、インターフェイスの概念がC#で導入されたのはそのためです。

抽象クラスの場合、多重継承もサポートされていません。したがって、抽象クラスまたはインターフェースを使用するかどうかを決めるのは簡単です。


正確には、C#で「インターフェイス」と呼ばれる純粋な抽象クラスです。
ヨハンブール

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.