重複の可能性:
インターフェイスvs基本クラス
C#クラスライブラリを設計するときに、インターフェイスより継承を選択する必要があるのはいつですか?
だから私はC#で最初の実際のプログラムを書いています。プログラムは、4つの異なるWebサイトからデータを取得します。私の計画は、次のような親クラスを1つ用意することです。
class Scraper
{
string scrapeDate(url);
string scrapeTime(url);
//&c.
}
次に、それから継承する4つのクラスを用意します。
もう1つのオプションは、Scraper
をインターフェイスにして、4つのクラスで実装することです。
これらのアプローチの違いは何ですか?
クラス継承は「is-a」関係を表します。たとえば、Tank
はVehicle
です。状況がこれを少なくとも満たしていない場合は、継承よりもインターフェイスを選択してください。
提案された基本クラスがメソッドのデフォルト実装を提供しない場合、これは継承よりもインターフェイスを選択するもう1つの理由になります。
C#では、1つのクラスからのみ継承できますが、複数のインターフェイスから継承できます。これは、継承よりもインターフェイスを選択するもう1つの理由です。
アイデアは?
私の意見では、継承は可能な場合はより良いアプローチです。基本クラスに共通の機能を実装すると、基盤となる実装の一貫性が保証されますが、インターフェイスを実装すると、インターフェイスの一貫性が保証されます。継承は可能な限りOOP三脚の1つのレッグであり、コードの重複を制限します。
共通の基本クラスを持つことができない、または持つべきではないが、同様の機能を提供する必要があるオブジェクトがある場合は、インターフェイスを使用します。共通のインターフェースを実装するクラスは、追加機能を公開する可能性があり(おそらくそうするでしょう)、複数のインターフェースを実装する場合もあります。
たとえば、あるアプリケーションでは、データアクセスレイヤーは、ビジネスオブジェクトとデータベースプロキシオブジェクトをデータベースから隔離する「プロバイダー」クラスを中心に構築されています。ある例では、SQL Serverデータベースと対話するプロバイダーと、Oracle CRM On Demandと通信するプロバイダーがあります(別名、なぜ神なのか)。どちらも不可知論的なインターフェースを実装しているため、クライアントコードは、どのデータストアが処理しているか、またはそれぞれの処理の癖を気にしません。
Oracleプロバイダーは、Webサービスのコレクションを介してホストサーバーと通信し、接続セッションを管理し、リクエストをバッチ処理します。SQLプロバイダーは、一連のストアドプロシージャを使用します。どちらのインターフェースも同じデータクラスを受け入れますが、Oracleプロバイダーはデータを変換して、難解な(簡単に言うと)スキーマに一致させます。この実装により、XMLデータストア、別のRDBMS、またはスタブ/モックユニットテストを使用するプロバイダーを簡単に追加できました。
この状況では、これらのものに共通の基本クラスがあることはあまり意味がなく、場合によっては不可能です。
正直なところ、両方を行います。
public interface IScraper
{
string ScrapeDate(string url);
}
public abstract class Scraper : IScraper
{
public string ScrapeDate(string url)
{
// default implementation
}
}
どちらの方法にも利点がありますが、要件について詳しく知ることなくそれらを定量化することは困難です。ただし、両方を実行できない理由はありません。クラスのインターフェースがあると、テスト目的でもモック可能になります。
ただし、他に考慮すべきこと派生クラスのそれぞれの機能が十分に類似している場合は、コンストラクターへのパラメーターを取る単一のクラスを用意する方が簡単な場合があります。
インターフェイスには、メソッド、デリゲート、またはイベントのシグネチャのみが含まれます。メソッドの実装は、インターフェースを実装するクラスで行われます。
クラスは複数のインターフェースを実装できます。
クラスは直接の基本クラスを1つだけ持つことができます。
インターフェースにより、一般的な動作の構造を定義できます。
継承は、1つ以上の特定の動作の一般的な実装を抽出できる場合に役立ちます。
基本的に、複数のクラスが同じ方法で日付をスクレイピングする場合、scrapeDateを基本クラスに配置することは理にかなっています。それ以外の場合は、インターフェースのみを使用し、インターフェースを実装するすべてのクラスで特定のscrapeDateを定義します。
共通の機能がある場合は、継承を使用する必要があります。この機能はすべての子クラスで使用でき、各子クラスは親クラスコードを拡張またはオーバーライドできます。
クラスを消費するものがある場合は、インターフェイスを使用して、すべてのクラスが同じメソッドとプロパティを実装するようにしますが、必ずしも同じ機能を実装する必要はありません。
主な違い:
あなたの場合、すべてのスクレイパークラスにいくつかの共通の機能が必要になる可能性が高いので、すべてのクラスに共通の基本クラスを継承させることは理にかなっています
抽象クラスとインターフェイス を参照してください。
インターフェイスと抽象クラスの間には、いくつかの類似点と相違点があります。
クラスはいくつかのインターフェースを実装するかもしれません。
クラスは1つの抽象クラスのみを継承できます。インターフェイスはコードを提供できず、署名のみを提供します。
抽象クラスは、完全なデフォルトコードおよび/またはオーバーライドする必要がある詳細のみを提供できます。インターフェイスは、サブ、関数、プロパティなどのアクセス修飾子を持つことはできませんすべてがパブリックとして想定されます
抽象クラスには、サブルーチン、関数、プロパティのアクセス修飾子を含めることができますインターフェイスは、クラスの周辺機能を定義するために使用されます。つまり、HumanとVehicleはどちらもIMovableインターフェースから継承できます。
抽象クラスは、クラスのコアアイデンティティを定義し、そこで同じタイプのオブジェクトに使用されます。さまざまな実装がメソッドシグネチャのみを共有する場合は、インターフェイスを使用することをお勧めします。
さまざまな実装が同じ種類であり、共通の動作またはステータスを使用する場合、抽象クラスを使用する方が適しています。インターフェイスに新しいメソッドを追加する場合、インターフェイスのすべての実装を追跡し、新しいメソッドの実装を定義する必要があります。
新しいメソッドを抽象クラスに追加する場合、デフォルトの実装を提供するオプションがあるため、既存のコードはすべて正しく機能する可能性があります。インターフェースでフィールドを定義することはできません
抽象クラスには、フィールドと定数を定義できます
ここにあるように見えるのは、本当にインターフェースとして機能するのに最適です。組み込みたいいくつかの一般的なロジックまたは組み込みたいいくつかの一般的なデータメンバーがある場合は、基本クラスを使用してそれから継承します。あなたがしていることは、各子に最低限のロジックのセットを実装することを要求しています。