web-dev-qa-db-ja.com

C#クラスライブラリを設計するときに、インターフェイスよりも継承を選択する必要があるのはいつですか?

2つの非常に異なることを行うProcessorクラスがいくつかありますが、一般的なコード(「制御の反転」状況)から呼び出されます。

それらがすべてBaseProcessorを継承するか、インターフェースとしてIProcessorを実装するかを決定する際に、どの設計上の考慮事項(ユーザーにとっては認識すべき)を考えているのでしょうか。

64

通常、ルールは次のようになります。

  • 継承は、is-a関係を表します。
  • インターフェイスの実装では、can-do関係について説明します。

これをもう少し具体的に言うと、例を見てみましょう。 System.Drawing.Bitmap classis-anイメージ(そして、それはImageから継承しますクラス)、ただし、can-do破棄も行うため、 IDisposable インターフェイスを実装します。また、can-doシリアル化なので、 ISerializable インターフェイスから実装します。

しかし実際には、C#で多重継承をシミュレートするためにインターフェイスがよく使用されます。 ProcessorクラスがSystem.ComponentModel.Componentなどから継承する必要がある場合、IProcessorインターフェイスを実装する以外に選択肢はほとんどありません。

実際には、インターフェイスと抽象基本クラスの両方が、特定のクラスが実行できることを指定するコントラクトを提供します。この契約を宣言するためにインターフェースが必要であるというのはよくある神話ですが、それは正しくありません。私の最大の利点は、抽象基本クラスを使用すると、サブクラスにデフォルトの機能を提供できることです。しかし、意味のあるデフォルトの機能がない場合、メソッド自体をabstractとしてマークすることを妨げるものは何もありません。インターフェースを実装する場合と同様に、派生クラスがそれ自体を実装する必要があります。

このような質問への回答については、 。NET Framework Design Guidelines によく目を向けます。

一般に、クラスは抽象化を公開するための好ましい構成です。

インターフェイスの主な欠点は、APIの進化を可能にすることに関して、クラスよりも柔軟性がはるかに低いことです。インターフェースを出荷すると、そのメンバーのセットは永久に修正されます。インターフェイスに追加すると、インターフェイスを実装する既存の型が壊れます。

クラスははるかに柔軟性があります。すでに出荷したクラスにメンバーを追加できます。メソッドが抽象的でない限り(つまり、メソッドのデフォルト実装を提供する限り)、既存の派生クラスは変更されずに機能し続けます。

[。 。 。 ]

インターフェイスを支持する最も一般的な議論の1つは、実装からコントラクトを分離できることです。ただし、引数は、クラスを使用して実装からコントラクトを分離できないと誤って想定しています。具体的な実装とは別のアセンブリに存在する抽象クラスは、このような分離を実現するための優れた方法です。

一般的な推奨事項は次のとおりです。

  • Doは、インターフェースよりもクラスの定義を優先します。
  • Doは、インターフェースの代わりに抽象クラスを使用して、コントラクトを実装から切り離します。抽象クラスは、正しく定義されていれば、コントラクトと実装を同じ程度に分離できます。
  • Do値型の多態的な階層を提供する必要がある場合、インターフェイスを定義します。
  • Considerインターフェイスを定義して、多重継承と同様の効果を実現します。

クリス・アンダーソンは、この最後の教義との特定の合意を表明し、次のように主張しています。

抽象型はバージョンがはるかに優れており、将来の拡張性を考慮していますが、唯一の基本型も焼き付けます。インターフェースは、2つのオブジェクト間の契約を実際に定義するときに適切であり、その契約は時間とともに不変です。タイプのファミリーの共通ベースを定義するには、抽象ベースタイプの方が適しています。

162
Cody Gray

リチャード、

なぜそれらを選択するのですか? published type(システムの他の場所で使用するため)としてIProcessorインターフェイスがあります。また、IProcessorのさまざまなCURRENT実装に共通の動作がある場合は、抽象BaseProcessorクラスがその共通の動作を実装するのに最適な場所になります。

このようにして、将来BaseProcessorのサービス用ではないIProcessorが必要になった場合、それを保持する必要はありません(そして、おそらくそれを非表示にします)...重複したコード/概念で。

ただ私の謙虚な意見。

乾杯。キース。

8
corlettk

インターフェースは「契約」であり、これらはいくつかのクラスがプロパティ、メソッド、イベントなどのメンバーの望ましいセットを実装することを保証します。

基本クラス(具象でも抽象でも構いません)は、あるエンティティの原型です。これは、実際の物理的または概念的なものに共通するものを表すエンティティです。

インターフェイスを使用する場合

何らかのタイプが、少なくとも消費者が気にし、それらを使用して何らかのタスクを達成する必要があるいくつかの動作とプロパティがあることを宣言する必要があるときはいつでも。

基本クラス(コンクリートおよび/または抽象)を使用する場合

エンティティのグループが同じアーキタイプを共有する場合は常に、BはAに違いがあるためBはAを継承しますが、BはAとして識別できます。


例:

テーブルについて話しましょう。

  • リサイクル可能なテーブルを受け入れます =>これは、「IRecyclableTable」などのインターフェイスで定義する必要があり、すべてのリサイクル可能なテーブルに「リサイクル」メソッドが設定されるようにします

  • デスクトップテーブルが必要です =>これは継承で定義する必要があります。 「デスクトップテーブル」は「テーブル」です。すべてのテーブルには共通のプロパティと動作があり、デスクトップのものには同じものがあり、デスクトップテーブルが他のタイプのテーブルとは異なる動作をするようなものを追加します。

オブジェクトグラフで両方の場合の意味である関連について話すことができますが、私の謙虚な意見では、概念的な観点で議論をする必要がある場合、私はこの議論で正確に答えます。

4

私はデザインの選択があまり得意ではありませんが、質問があれば、拡張するメンバーのみがいる場合はiProcessorインターフェースを実装することを好みます。拡張する必要のない他の関数がある場合は、ベースプロセッサから継承することをお勧めします。

1
jitendragarg

デザインの純粋さは別として、一度しか継承できないので、何らかのフレームワーククラスを継承する必要がある場合、問題は議論の余地があります。

選択肢がある場合は、実用的には、ほとんどの入力を節約するオプションを選択できます。とにかく、通常は純粋主義者の選択です。

編集:

基本クラスに何らかの実装がある場合、これは有用である可能性があります。もしそれが純粋に抽象的であれば、インターフェースである可能性があります。

1
Jodrell

選択した内容が重要でない場合は、常にインターフェイスを選択してください。これにより、柔軟性が高まります。将来の変更から保護される可能性があります(基本クラスで何かを変更する必要がある場合、継承されたクラスが影響を受ける可能性があります)。また、詳細をより適切にカプセル化できます。依存性注入の制御の反転を使用している場合、インターフェイスを優先する傾向があります。

または、継承を回避できない場合は、おそらく両方を一緒に使用することをお勧めします。インターフェイスを実装する抽象基本クラスを作成します。あなたの場合、ProcessorBaseはIProcessorを実装しています。

ASP.NET MvcのControllerBaseおよびIControllerに似ています。

0
Hendry Ten

SOLID原則がプロジェクトの保守性と拡張性を高めることを考えると、継承よりもインターフェイスを好むでしょう。

また、インターフェイスに「追加機能」を追加する必要がある場合、最善のオプションは、SOLIDのIに従って、新しいインターフェイスを完全に作成することです。

0