インターフェイスと基本クラス


767

いつインターフェイスを使用する必要があり、いつ基本クラスを使用する必要がありますか?

メソッドの基本実装を実際に定義したくない場合は、常にインターフェースにする必要がありますか?

犬と猫のクラスがある場合。PetBaseの代わりにIPetを実装したいのはなぜですか?IShedsまたはIBarks(IMakesNoise?)のインターフェースをペットごとに配置できるため、それらを使用できることは理解できますが、一般的なペットにどのインターフェースを使用すればよいかわかりません。


11
考慮に入れるべきポイントだと思います-インターフェースには、ごく後期まで気付かないかもしれないいくつかの制限が課される可能性があります。たとえば、.NETではインターフェイスメンバー変数をシリアル化できないため、クラスZooとIAnimalsのメンバー変数配列がある場合、Zooをシリアル化できません(つまり、WebServicesまたはその他のシリアル化が必要なものを作成すると、痛み)。
synhershko

1
この質問は、インターフェースの概念を理解するのに役立ちます。stackoverflow.com/q/8531292/1055241
gprathour 2014

ただ興味があるだけ。私はC#を介しCLRで次の抜粋に出会いましたI tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation.。抜粋の意味がわかりません。いくつかの基本タイプを作成し、それらのいずれかの派生タイプを作成できるため、開発者は基本タイプを選択できます。誰かが説明してくれませんか、私は何が欠けていますか?私はそれがこの質問の一部であると信じています。または、特定の抜粋について別のものを投稿する必要がありますか?
qqqqqqq

回答:


501

DogクラスとCatクラスの例を見て、C#の使用方法を説明しましょう。

犬と猫はどちらも動物で、特に四足動物です(動物は非常に一般的です)。あなたはそれらの両方のためにあなたが抽象クラスMammalを持っていると仮定しましょう:

public abstract class Mammal

この基本クラスには、おそらく次のようなデフォルトのメソッドがあります。

  • フィード
  • メイト

これらはすべて、どちらかの種の間でほぼ同じ実装を持つ行動です。これを定義するには、次のようにします。

public class Dog : Mammal
public class Cat : Mammal

ここで、他の哺乳類がいると仮定しましょう。哺乳類は通常動物園で見られます。

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

これはまだ理由の機能の中核で有効になりますFeed()と、Mate()やはり同じになります。

ただし、キリン、サイ、カバは、ペットを作ることができる動物ではありません。ここでインターフェースが役立ちます。

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

上記の契約の実装は、猫と犬で同じではありません。それらの実装を抽象クラスに入れて継承するのは悪い考えです。

犬と猫の定義は次のようになります。

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

理論的には、上位の基本クラスからそれらをオーバーライドできますが、本質的には、インターフェイスを使用すると、継承する必要なく、必要なものだけをクラスに追加できます。

その結果、通常は1つの抽象クラスからのみ継承できます(ほとんどの静的型付きOO言語、つまり例外にはC ++が含まれます)が、複数のインターフェイスを実装できるため、必要に応じてオブジェクトを厳密に構築できます。


149
それはそんなに簡単なことではないと思います。質問(要件)を少し変更して、インターフェースがより意味をなすようにしました。コントラクト(インターフェイス)を定義しているか、共有実装(基本クラス)を定義しているかを常に自問する必要があります。
David Pokluda 2008

18
インターフェースは契約です。あなたはサービスが必要とする契約の部分だけを公開しています。「PettingZoo」を使用している場合は、「メイト」をユーザーに公開したくないはずです。
Anthony Mastrean

10
@David Touche。ただし、彼の理解に対して、インターフェイスの目的と抽象クラスの目的をより明確に示すために行いました。犬と猫は厳格な要件ではないようです!
Jon Limjap 2008

2
解釈されたJIT処理環境(特にJVM)では、仮想メソッド呼び出しは、コンテキストによってインターフェースメソッド呼び出しよりも大幅に高速であることに注意してください。JVMは多くの場合、低速なメソッド検索を最適化できるため、「コンテキスト」を強調します。(たとえば、インターフェイスの継承者のリストが同じクラスのインスタンスである傾向がある場合。これは悲しいことに、少しベンチマークを難しくします。)パフォーマンスの影響を受けやすいものを最適化しようとしている場合にのみ、これを検討する必要があります。
フィリップグイン

14
また、インターフェースはペットの岩を許可し、それを浴びてトリックを教えることを可能にしますが、それは岩にとってばかげているので、摂食と交尾はサポートされません。
eclux 2015

146

Josh Bloch氏は、Effective Java 2dで次のように述べています

抽象クラスよりもインターフェースを優先する

主なポイント:

  • 既存のクラスを簡単に改造して、新しいインターフェイスを実装できます。必要なメソッドが存在しない場合は必要なメソッドを追加し、クラス宣言にimplements句を追加するだけです。

  • インターフェイスは、ミックスインの定義に最適です。大まかに言えば、ミックスインはクラスがその「プライマリタイプ」に加えて実装できるタイプであり、いくつかのオプションの動作を提供することを宣言します。たとえば、Comparableは、クラスがそのインスタンスが他の相互に比較可能なオブジェクトに関して順序付けられていることを宣言できるようにするミックスインインターフェイスです。

  • インターフェイスにより、非階層型フレームワークを構築できます。タイプ階層は、いくつかのものを整理するのに最適ですが、他のものは厳密な階層にうまく分類されません。

  • インターフェイスは、ラッパークラスイディオムを介して安全で強力な機能拡張可能にします。抽象クラスを使用して型を定義する場合は、継承を使用する以外に機能を追加したくないプログラマーを任せます。

さらに、エクスポートする重要な各インターフェースに対応する抽象スケルトン実装クラスを提供することにより、インターフェースと抽象クラスの長所を組み合わせることができます。

一方、インターフェースは進化するのが非常に難しいです。メソッドをインターフェースに追加すると、その実装がすべて破壊されます。

PS .:本を購入します。より詳細です。


71
インターフェースに変更を加える必要があるときはいつでも、そうするための非破壊的な方法は、古いインターフェースから継承する新しいインターフェースを作成することです。これにより、既存の実装が保持され、新しい実装で必要なことをすべて実行できます。
スコットローレンス

5
「インターフェース分離原則」があります。この原則は、インターフェイスの作成方法に注意することを教えてくれます。インターフェイスを作成するときは、そこにあるはずのメソッドのみを追加するように注意する必要があります。そこにないはずのメソッドを追加する場合、インターフェースを実装するクラスもそれらのメソッドを実装する必要があります。たとえば、Workerというインターフェイスを作成し、メソッドランチブレークを追加した場合、すべてのワーカーがそれを実装する必要があります。ワーカーがロボットの場合はどうなりますか?結論として、固有でないメソッドを含むインターフェースは、汚染インターフェースまたはファットインターフェースと呼ばれます。
Eklavyaa

Java 8以降、デフォルトメソッドを使用すると、インターフェースに新しい機能を追加し、そのインターフェースを実装する既存のクラスの下位互換性を確保できます。実装クラスでオーバーライドされていない場合、デフォルトのメソッドがデフォルトで呼び出されます。すべての実装クラスは、デフォルトのメソッドをオーバーライドするか、instance.defaultMethod()を使用してそれらを直接呼び出すことができます
Arpit Rastogi

118

インターフェースと基本クラスは、2つの異なる形式の関係を表します。

継承(基本クラス)は「is-a」関係を表します。たとえば、犬や猫の「is-a」ペットです。この関係は常に、クラスの(単一の)目的を表します(「単一の責任の原則」と関連して)。

一方、インターフェイスは、クラスの追加機能を表します。「Foois disposable」のような「is」関係と呼ぶので、IDisposableC#のインターフェイスです。


10
すべての答えのうち、これは明瞭さを失うことなく簡潔さの最良の組み合わせを提供します
マットウィルコ2014

「has-a」関係がある場合、誰かがインターフェースを使用するように言ったことがあります。それがいつも正しいのかどうかはわかりません。私のラップトップは画面を持っているので、ラップトップはIScreenを実装するべきですか、それともScreenプロパティを持っているべきですか?後者の方が自然に思えます。
2014年

1
@berendおもしろいのは、画面がインターフェイスを介して実装されているため、VGA、HDMIなど
Konstantin

それがOOPであるからといって、現実のシナリオに従うには想像力を伸ばす必要があります。常に適用されるわけではありません。
クリスモグラム2018

110

モダンスタイルはIPet PetBase を定義することです。

このインターフェースの利点は、他のコードが他の実行可能コードとまったく関係なくそれを使用できることです。完全に「きれい」。また、インターフェイスを混在させることもできます。

ただし、基本クラスは、単純な実装と一般的なユーティリティに役立ちます。したがって、時間とコードを節約するために、抽象基本クラスも提供します。


ケーキを持って食べてね!
ダレンコップ

3
インターフェイスは、他のクラスがコードを使用する方法を定義します。基本クラスは、実装者がインターフェースを実装するのに役立ちます。2つの異なる目的のための2つの異なるもの。
Bill Michell、

27
ここには「モダン」なものは何もありません。同じAPIの基本クラスとインターフェースは、単に冗長です。このアプローチを使用する場合もありますが、一般化しないでください。
smentek 2011

3
2番目にサポートされている回答に対する最もサポートされているコメントは、実際には回答自体に同意していません。インターフェースと基本クラスの多くの例が共存しているのを見なければならない。その意味で、それは「モダン」な方法です。たとえば、MVVMパターンでは、実際にはINotifyPropertyChangedを実装するViewModelBaseクラスがあります。しかし、私の同僚は、なぜ私が代わりに、すべてのビューモデルにインタフェースを実装するので、私は彼を説得する方法がわからない、基本クラスを持っていなかった私に尋ねられたとき
テテ

1
正しい。どちらか一方の問題ではありません。それらは2つの非常に異なる問題に対処するために存在します。インターフェイスは、実装クラスが対処する必要があるコントラクトです。彼らは最近、IoCとTDDの側面に(時には狂信的に)好意を示しています。抽象/基本クラスは、階層的に共通するロジックとプロパティをグループ化するのに役立ちます。重複するコードが削減され、ソリューションの保守性が向上し、エラーが発生しにくくなります。
カムイン

63

インターフェース

  • 2つのモジュール間の規約を定義します。実装することはできません。
  • ほとんどの言語では、複数のインターフェースを実装できます
  • インターフェイスの変更は重大な変更です。すべての実装は再コンパイル/変更する必要があります。
  • すべてのメンバーは公開されています。実装はすべてのメンバーを実装する必要があります。
  • インターフェイスはデカップリングに役立ちます。モックフレームワークを使用して、インターフェイスの背後にあるものをモックアウトできます
  • インターフェイスは通常、一種の動作を示します
  • インターフェース実装は、互いに分離/分離されています

基本クラス

  • いくつかのデフォルトを追加できます派生によって無料で入手できる実装
  • C ++を除いて、1つのクラスからのみ派生できます。複数のクラスからできたとしても、それは通常悪い考えです。
  • 基本クラスの変更は比較的簡単です。派生物は特別なことをする必要はありません
  • 基本クラスは、派生によってアクセスできる保護された関数とパブリック関数を宣言できます
  • 抽象基本クラスはインターフェースのように簡単にモックすることはできません
  • 基本クラスは通常、タイプ階層(IS A)を示します
  • クラスの派生は、いくつかの基本動作に依存するようになる可能性があります(親の実装について複雑な知識を持っています)。1人の人の基本実装に変更を加え、他の人を壊すと、事態が混乱する可能性があります。

注:フレームワークの設計ガイドラインでは、バージョンの方が優れているため、インターフェイスではなく基本クラスを使用することをお勧めします。vNextにおける抽象基底クラスに新しいメソッドを追加すると、非破壊変更..です
Gishu

59

一般に、抽象クラスよりもインターフェースを優先する必要があります。抽象クラスを使用する1つの理由は、具象クラス間で共通の実装がある場合です。もちろん、インターフェイス(IPet)を宣言し、抽象クラス(PetBase)にそのインターフェイスを実装させる必要があります。小さくて異なるインターフェイスを使用すると、複数を使用して柔軟性をさらに向上させることができます。インターフェースにより、境界を越えたタイプの柔軟性と移植性の最大量が可能になります。境界を越えて参照を渡すときは、具象型ではなく、常にインターフェースを渡します。これにより、受信側は具体的な実装を決定でき、最大限の柔軟性が提供されます。これは、TDD / BDD方式でプログラミングする場合には絶対に当てはまります。

ギャングオブフォーは彼らの本の中で述べています。私はこれが真実であると信じています。


イェッシュ。個人的には、これはお尻だと思います。インターフェイスは型の最低限の機能を保持する必要があり、基本クラスはカスタマイズを構築できる豊富なフレームワークを提供する必要があります。それをインターフェースに入れれば、実装は非常に難しくなります。

あなたのインターフェースは巨大である必要があると誰も言っていません。小さくて複数のインターフェースと豊富な基本クラスが優れたAPIを作ります。
Kilhoffer、2009

3
それは私だけですか、それともほとんどの「一般的なワーカー」クラスは一般的な実装を共有していますか?そのような状況では、インターフェースを優先するという一般的なルールに反します。あなたの一般化を2つの一般化に言い換えます。ロジックをまったくまたはほとんど含まないこれらの一般的なクラスは、インターフェースを実装する必要があります。「まともな」量のロジックを含むこれらの一般的なクラスは、基本クラスから派生する必要があります(機能を共有する可能性が高いため)。
goku_da_master

@Kilhoffer「インターフェースにより、境界を越えて型の柔軟性と移植性を最大限に高めることができます」このステートメントについて詳しく説明してください。
JAVA

49

これはかなり.NET固有ですが、「フレームワーク設計ガイドライン」の本は、一般的にクラスは進化するフレームワークにより多くの柔軟性を与えると主張しています。インターフェースが出荷されると、そのインターフェースを使用したコードを壊さずにインターフェースを変更することはできません。ただし、クラスを使用すると、クラスを変更することができ、それにリンクしているコードを壊すことはありません。新しい機能の追加を含む適切な変更を行う限り、コードを拡張および進化させることができます。

Krzysztof Cwalinaは81ページでこう言っています:

.NET Frameworkの3つのバージョンの過程で、私はこのガイドラインについてチームのかなりの数の開発者と話しました。最初にガイドラインに同意しなかった人を含め、それらの多くは、一部のAPIをインターフェースとして出荷したことを後悔していると述べています。誰かがクラスを出荷したことを後悔した例も1つも聞いたことがありません。

そうは言っても、インターフェースの場所は確かにあるのです。一般的なガイドラインとして、インターフェースを実装する方法の例として他に何もない場合は、常にインターフェースの抽象基本クラス実装を提供します。最良の場合、その基本クラスは多くの作業を節約します。


19

ファン、

インターフェイスをクラスを特徴付ける方法として考えるのが好きです。特定の犬種クラス、たとえばYorkshireTerrierは、親犬クラスの子孫である可能性がありますが、IFurry、IStubby、およびIYippieDogも実装しています。したがって、クラスはクラスが何であるかを定義しますが、インターフェースはそれについて私たちに伝えます。

これの利点は、たとえば、すべてのIYippieDogを収集して、それらをOceanコレクションに投入できることです。これで、クラスを詳しく調べなくても、特定のオブジェクトセットに到達して、見ている基準を満たすオブジェクトを見つけることができます。

インターフェースは本当にクラスのパブリック動作のサブセットを定義するべきだと私は思います。実装するすべてのクラスのすべてのパブリック動作を定義する場合、通常は存在する必要はありません。彼らは私に何か有用なことを教えてくれません。

ただし、この考えは、すべてのクラスにインターフェイスが必要であり、そのインターフェイスにコーディングする必要があるという考えに反します。それは結構ですが、クラスへの1対1のインターフェースがたくさんあり、混乱を招きます。私はそれが実際には何もする必要がなく、今では簡単に物事を入れ替えることができるという考えだと理解しています。しかし、私はめったにそれをしません。ほとんどの場合、既存のクラスを変更するだけで、そのクラスのパブリックインターフェイスを変更する必要がある場合は常に同じ問題を抱えていますが、2か所で変更する必要があります。

したがって、私のように考えるなら、猫と犬はIPettableであると間違いなく言うでしょう。それらの両方に一致する特性です。

これの他の部分は、同じ基本クラスを持つべきですか?問題は、それらを広く同じものとして扱う必要があるかどうかです。確かにどちらも動物ですが、一緒に使用する方法に適しています。

動物のクラスをすべて集めて、Arkコンテナに入れたいとしましょう。

それとも哺乳類である必要がありますか?おそらく、ある種のクロス動物搾乳工場が必要でしょうか?

それらはまったく一緒にリンクされる必要さえありますか?両方がIPettableであることを知っていれば十分ですか?

クラスが1つだけ必要なときに、クラス階層全体を導出したいという欲求をよく感じます。私はいつかそれが必要になるかもしれないと予想してそれをします、そして通常私は決してしません。私がそうするときでさえ、私は通常それを修正するために多くのことをしなければならないことに気づきます。それは、私が作成している最初のクラスが犬ではないためです。私はそれほど幸運ではなく、代わりにカモノハシです。現在、私のクラス階層全体は奇妙なケースに基づいており、無駄なコードがたくさんあります。

また、ある時点で、すべての猫がIPettableであるとは限らない場合もあります(その無毛の猫のように)。これで、そのインターフェースを、適合するすべての派生クラスに移動できます。突然のすべての猫が、PettableBaseから派生しなくなったことよりもはるかに重大な変更であることがわかります。


18

インターフェイスと基本クラスの基本的で単純な定義は次のとおりです。

  • 基本クラス=オブジェクトの継承。
  • インターフェース=機能継承。

乾杯


12

可能な限り、継承ではなくコンポジションを使用することをお勧めします。インターフェイスを使用しますが、基本実装にはメンバーオブジェクトを使用します。このようにして、特定の方法で動作するオブジェクトを構築するファクトリを定義できます。動作を変更する場合は、さまざまなタイプのサブオブジェクトを作成する新しいファクトリメソッド(または抽象ファクトリ)を作成します。

場合によっては、すべての変更可能な動作がヘルパーオブジェクトで定義されていると、プライマリオブジェクトがインターフェイスをまったく必要としないことがあります。

そのため、IPetやPetBaseの代わりに、IFurBehaviorパラメータを持つペットになる可能性があります。IFurBehaviorパラメータは、PetFactoryのCreateDog()メソッドによって設定されます。shed()メソッドで呼び出されるのはこのパラメーターです。

これを行うと、コードがはるかに柔軟になり、単純なオブジェクトのほとんどがシステム全体の非常に基本的な動作を処理します。

多重継承言語でもこのパターンをお勧めします。


12

これは、このJava Worldの記事で詳しく説明されています

個人的に、私はインターフェースを定義するためにインターフェースを使用する傾向があります。つまり、何かにアクセスする方法を指定するシステム設計の一部です。

1つまたは複数のインターフェイスを実装するクラスが存在することも珍しくありません。

他の何かの基礎として使用する抽象クラス。

以下は、上記の記事JavaWorld.comの記事、著者Tony Sintes、04/20/01からの抜粋です。


インターフェースと抽象クラス

インターフェイスと抽象クラスを選択することは、どちらかまたは両方の命題ではありません。デザインを変更する必要がある場合は、インターフェースにします。ただし、デフォルトの動作を提供する抽象クラスがある場合があります。抽象クラスは、アプリケーションフレームワーク内の優れた候補です。

抽象クラスでは、いくつかの動作を定義できます。それらはあなたのサブクラスに他のものを提供することを強制します。たとえば、アプリケーションフレームワークがある場合、抽象クラスはイベントやメッセージの処理などのデフォルトサービスを提供します。これらのサービスにより、アプリケーションをアプリケーションフレームワークにプラグインできます。ただし、アプリケーションのみが実行できるアプリケーション固有の機能がいくつかあります。このような機能には、多くの場合アプリケーションに依存する起動およびシャットダウンタスクが含まれる場合があります。したがって、その動作自体を定義しようとする代わりに、抽象基本クラスは、抽象シャットダウンおよび起動メソッドを宣言できます。基本クラスはそれらのメソッドが必要であることを認識していますが、抽象クラスはクラスにそれらのアクションの実行方法を認識していないことを認めさせます。アクションを開始する必要があることを知っているだけです。立ち上がる時が来たら 抽象クラスは、スタートアップメソッドを呼び出すことができます。基本クラスがこのメソッドを呼び出すと、Javaは子クラスによって定義されたメソッドを呼び出します。

多くの開発者は、抽象メソッドを定義するクラスがそのメソッドを呼び出すこともできることを忘れています。抽象クラスは、計画された継承階層を作成するための優れた方法です。また、クラス階層の非リーフクラスにも適しています。

クラスとインターフェース

すべてのクラスをインターフェイスの観点から定義する必要があると言う人もいますが、推奨は少し極端に思えます。デザインの一部が頻繁に変更されることがわかったときにインターフェイスを使用します。

たとえば、Strategyパターンを使用すると、新しいアルゴリズムとプロセスを、それらを使用するオブジェクトを変更せずにプログラムに入れ替えることができます。メディアプレーヤーは、CD、MP3、およびwavファイルの再生方法を知っている場合があります。もちろん、これらの再生アルゴリズムをプレーヤーにハードコーディングする必要はありません。そのため、AVIなどの新しい形式を追加するのが難しくなります。さらに、コードは無用なcaseステートメントで散らかされます。また、傷害に侮辱を加えるには、新しいアルゴリズムを追加するたびにそれらのケースステートメントを更新する必要があります。全体として、これはオブジェクト指向のプログラミング方法ではありません。

Strategyパターンを使用すると、オブジェクトの背後にアルゴリズムを単純にカプセル化できます。その場合、いつでも新しいメディアプラグインを提供できます。プラグインクラスをMediaStrategyと呼びましょう。そのオブジェクトには、playStream(Stream s)という1つのメソッドがあります。新しいアルゴリズムを追加するには、アルゴリズムクラスを拡張するだけです。プログラムが新しいメディアタイプに遭遇すると、ストリームの再生をメディア戦略に委任するだけです。もちろん、必要なアルゴリズム戦略を適切にインスタンス化するには、いくつかの配管が必要です。

これは、インターフェースを使用するのに最適な場所です。Strategyパターンを使用しました。これは、デザイン内の変更される場所を明確に示しています。したがって、戦略をインターフェースとして定義する必要があります。オブジェクトに特定のタイプを持たせたい場合は、一般に継承よりもインターフェースを優先する必要があります。この場合はMediaStrategyです。型IDの継承に依存することは危険です。特定の継承階層に固定されます。Javaでは多重継承が許可されていないため、便利な実装やより多くの型IDを提供する拡張はできません。


2
+1。「型IDの継承に依存するのは危険です。特定の継承階層にロックされます。」その文は、インターフェースを好む私の理由を完全に説明しています。
エンジニア、

さらに、拡張するのではなく、インターフェースの各メソッドの背後にある実装を構成します。
エンジニア、

10

また、オブジェクト指向で流されないように(ブログを参照)、常に必要な動作に基づいてオブジェクトをモデル化してください。必要な動作が動物の一般的な名前と種のみであるアプリを設計している場合は、必要なのは世界中のあらゆる動物の数百万のクラスではなく、名前のプロパティを持つ1つのクラスの動物。


10

大まかな目安があります

機能:すべての部分で異なる可能性があります:インターフェイス。

データと機能、パーツはほとんど同じですが、パーツは異なります:抽象クラス。

わずかな変更のみで拡張した場合、実際に機能するデータと機能:通常の(コンクリート)クラス

データと機能、変更予定なし: final修飾子を持つ通常の(コンクリート)クラス。

データ、そしておそらく機能:読み取り専用:列挙型メンバー。

これは非常にラフで準備が整っており、厳密に定義されているわけではありませんが、すべてを変更することを意図したインターフェイスから、すべてが読み取り専用ファイルのように少し修正された列挙型にスペクトルがあります。


7

インターフェイスは小さくする必要があります。本当に小さい。あなたが本当にオブジェクトを分解しているなら、あなたのインターフェースはおそらくいくつかの非常に特定のメソッドとプロパティしか含まないでしょう。

抽象クラスはショートカットです。PetBaseのすべての派生物が共有できる、一度コーディングすれば済むことはありますか?はいの場合は、抽象クラスの時間です。

抽象クラスにも制限があります。子オブジェクトを作成するための優れたショートカットを提供しますが、特定のオブジェクトが実装できる抽象クラスは1つだけです。多くの場合、これは抽象クラスの制限であると思います。そのため、私は多くのインターフェースを使用しています。

抽象クラスにはいくつかのインターフェースが含まれている場合があります。PetBase抽象クラスは、IPet(ペットには所有者がいる)とIDigestion(ペットは食べるか、少なくともそうするべきです)を実装する場合があります。ただし、すべてのペットが哺乳動物であるとは限らず、すべての哺乳動物がペットであるとは限らないため、PetBaseはおそらくIMammalを実装しません。PetBaseを拡張するMammalPetBaseを追加し、IMammalを追加できます。FishBaseはPetBaseを持ち、IFishを追加できます。IFishはインターフェースとしてISwimとIUnderwaterBreatherを持ちます。

はい、私の例は単純な例に比べて非常に複雑ですが、それはインターフェイスと抽象クラスがどのように連携するかについての素晴らしいことの一部です。


7

出典 http //jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C#は、過去14年間で成熟し進化した素晴らしい言語です。成熟した言語は、私たちの自由に使える言語機能をたくさん提供するので、これは私たちの開発者にとって素晴らしいことです。

しかし、多くの力で多くの責任になります。これらの機能の一部は誤用される可能性があり、ある機能を別の機能で使用することを選択する理由を理解するのが難しい場合があります。何年にもわたって、多くの開発者が苦労してきた機能の1つは、いつインターフェースを使用するか、または抽象クラスを使用するかを選択することです。どちらにも長所と短所があり、それぞれを使用する正しい時間と場所があります。しかし、どうやって決めるのですか?

どちらもタイプ間の共通機能の再利用を提供します。最も明らかな違いは、インターフェースは機能の実装を提供しないことですが、抽象クラスでは「基本」または「デフォルト」の動作を実装し、必要に応じてクラスの派生型でこのデフォルトの動作を「オーバーライド」することができます。 。

これはすべてうまくいっており、コードの再利用に優れており、ソフトウェア開発のDRY(Do n't Repeat Yourself)原則に準拠しています。抽象クラスは、「ある」関係がある場合に使用すると便利です。

例:ゴールデンレトリバーは「ある」タイプの犬です。プードルもそうです。すべての犬がそうであるように、彼らは両方とも吠えることができます。ただし、プードルパークは「デフォルト」の犬の樹皮とは大幅に異なると述べたい場合があります。したがって、次のように実装することは理にかなっています。

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

ご覧のとおり、これは、コードをDRYに保ち、いずれかの型が特別なケースの実装ではなくデフォルトのBarkに依存できるときに基本クラスの実装を呼び出すことができる優れた方法です。GoldenRetriever、Boxer、Labなどのクラスは、Dog抽象クラスを実装しているという理由だけで、すべて「デフォルト」(低音クラス)Barkを無料で継承できます。

しかし、あなたはすでにそれを知っていたと思います。

あなたがここにいるのは、抽象クラスではなくインターフェースを選択する理由、またはその逆の理由を理解するためです。抽象クラスではなくインターフェイスを選択する理由の1つは、デフォルトの実装がないか、デフォルトの実装を禁止したい場合です。これは通常、「is a」関係で関連付けられていないインターフェースを実装しているタイプが原因です。実際、それぞれのタイプが「できる」、または何かをしたり、持ったりする「能力」を持っているという事実を除いて、それらはまったく関連している必要はありません。

これは一体何を意味するのでしょうか?たとえば、人間はアヒルではありません。アヒルは人間ではありません。かなり明白。ただし、アヒルと人間の両方に「泳ぐ能力」があります(人間が1年生で水泳のレッスンに合格した場合を想定します)。また、アヒルは人間ではない、またはその逆であるため、これは「is a」実現ではなく、「isable」関係であり、インターフェースを使用して次のことを説明できます。

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

上記のコードのようなインターフェースを使用すると、何かを「実行できる」メソッドにオブジェクトを渡すことができます。コードはそれがどのように行われるかを気にしません...そのオブジェクトでSwimメソッドを呼び出すことができ、そのオブジェクトはその型に基づいて実行時にどの動作を実行するかを知っているということだけがわかります。

繰り返しになりますが、これによりコードが乾燥したままになるため、同じコア関数(ShowHowHumanSwims(human)、ShowHowDuckSwims(duck)など)を実行するためにオブジェクトを呼び出す複数のメソッドを記述する必要がなくなります。

ここでインターフェースを使用すると、呼び出し側のメソッドは、どのタイプか、または動作がどのように実装されるかについて心配する必要がなくなります。インターフェースが与えられれば、各オブジェクトはSwimメソッドを実装する必要があるため、それを独自のコードで呼び出して、独自のクラス内でSwimメソッドの動作を処理できることがわかっています。

概要:

したがって、私の主な経験則は、クラス階層の「デフォルト」機能を実装する場合に抽象クラスを使用すること、および/または使用しているクラスまたはタイプが「is a」関係を共有することです(例:プードル「is a犬のタイプ)。

一方、「is a」関係はないが、何かをする「能力」を共有するタイプまたは何かを持っているタイプ(たとえば、Duckは人間ではない)の場合にインターフェイスを使用します。ただし、アヒルと人間の共有「泳ぐ能力」)。

抽象クラスとインターフェースのもう1つの違いは、クラスは1対多のインターフェースを実装できますが、クラスは1つの抽象クラス(または、任意のクラス)からしか継承できないことです。はい、クラスをネストして継承階層を持つことができます(多くのプログラムが持っているはずです)が、1つの派生クラス定義で2つのクラスを継承することはできません(このルールはC#に適用されます。他の一部の言語では、通常これを行うことができます。これらの言語にはインターフェースがないためだけです)。

また、インターフェイスを使用して、インターフェイス分離原則(ISP)に準拠する場合にも注意してください。ISPは、クライアントが使用しないメソッドに依存することを強制されるべきではないと述べています。このため、インターフェイスは特定のタスクに焦点を当てる必要があり、通常は非常に小さくなります(例:IDisposable、IComparable)。

もう1つのヒントは、機能の小さな簡潔なビットを開発する場合は、インターフェースを使用することです。大規模な機能ユニットを設計する場合は、抽象クラスを使用します。

これで一部の人の問題が解決することを願っています!

また、より良い例を思いついたり、何か指摘したい場合は、下のコメントでそのようにしてください!


6

インターフェイスを介した基本クラスの事例は、サブメイン.NETコーディングガイドラインで詳しく説明されています。

基本クラスとインターフェイス インターフェースタイプは、値の部分的な説明であり、多くのオブジェクトタイプでサポートされる可能性があります。可能な限り、インターフェイスではなく基本クラスを使用してください。バージョン管理の観点から見ると、クラスはインターフェースよりも柔軟性があります。クラスを使用すると、バージョン1.0を出荷してから、バージョン2.0でクラスに新しいメソッドを追加できます。メソッドが抽象的でない限り、既存の派生クラスは変更されずに機能し続けます。

インターフェースは実装の継承をサポートしていないため、クラスに適用されるパターンはインターフェースには適用されません。メソッドをインターフェイスに追加することは、抽象メソッドを基本クラスに追加することと同じです。クラスが新しいメソッドを実装しないため、インターフェイスを実装するクラスはすべて機能しなくなります。インターフェイスは次の状況に適しています。

  1. いくつかの無関係なクラスがプロトコルをサポートしたいと考えています。
  2. これらのクラスには既に基本クラスが設定されています(たとえば、一部はユーザーインターフェイス(UI)コントロールであり、一部はXML Webサービスです)。
  3. 集計は適切でも実用的でもありません。他のすべての状況では、クラス継承がより優れたモデルです。

この答えはもっと注目されるべきだと思います。ここでは、多くの答えの粒度に反しています。私は完全に同意するとは言いませんが、ここで素晴らしい点があります。
kayleeFrye_onDeck

5

重要な違いの1つは、継承できる基本クラスは1つだけですが、多くのインターフェースを実装できることです。したがって、別の基本クラスも継承する必要がないことが確実である場合にのみ、基本クラスを使用します。さらに、インターフェースが大きくなっていることがわかった場合は、独立した機能を定義するいくつかの論理的な部分にそれを分割することを検討する必要があります。これは、クラスがそれらすべてを実装できない(または別の機能を定義できる)ルールがないためです。それらをすべて継承してグループ化するインターフェース)。


4

最初にオブジェクト指向プログラミングについて学び始めたとき、継承を使用して共通の動作を共有するという、簡単でおそらくありがちな間違いを犯しました。

この特定の質問でよく使用される例をさらに発展させるために、ガールフレンド、車、ファジィブランケットなど、ペットに適したものがたくさんあります。このため、この共通の動作を提供するPetableクラスと、継承するさまざまなクラスがあったかもしれませんそれから。

ただし、可食性であることは、これらのオブジェクトの性質の一部ではありません。非常に多くの重要な概念があります、その性質に不可欠では-のガールフレンドは、猫が哺乳動物である、車は陸上車両で、人であります...

動作は最初にインターフェース(クラスのデフォルトインターフェースを含む)に割り当てられ、(a)より大きなクラスのサブセットであるクラスの大きなグループに共通である場合にのみ、基本クラスに昇格する必要があります。 「猫」と「人」は「哺乳類」のサブセットです。

問題は、私が最初にオブジェクト指向の設計を十分に理解した後で、通常はそれについて何も考えずに自動的に行うことです。したがって、「抽象クラスではなくインターフェイスへのコード」というステートメントの真実が明らかになるので、だれかがわざわざ言って他の意味を読み取ろうとするのを信じるのは難しい。

追加したいもう1つのことは、クラスが純粋に抽象的である場合-非抽象、非継承のメンバーまたはメソッドが子、親、またはクライアントに公開されていない場合-なぜそれがクラスなのか?場合によってはインターフェースに置き換えられ、別の場合にはNullに置き換えられます。


純粋に抽象クラスは、メソッドにデフォルトの動作を提供できます。これは、具象クラスがすべて、何度も再実装するのに冗長になる共通のメソッドを共有する場合に役立ちます。
アダムヒューズ

4

抽象クラスよりもインターフェースを優先する

理論的根拠、考慮すべき主なポイントは次の2つです。

  • クラスは複数のインターフェースを実装できるため、インターフェースはより柔軟です。Javaには多重継承がないため、抽象クラスを使用すると、ユーザーは他のクラス階層を使用できなくなります。一般に、デフォルトの実装または状態がない場合はインターフェースを優先します。Javaコレクションは、この良い例(マップ、セットなど)を提供します。
  • 抽象クラスには、上位互換性が向上するという利点があります。クライアントがインターフェイスを使用すると、それを変更することはできません。抽象クラスを使用している場合でも、既存のコードを壊すことなく動作を追加できます。互換性が問題になる場合は、抽象クラスの使用を検討してください。
  • デフォルトの実装や内部状態がある場合でも 、インターフェースとその抽象的な実装を提供することを検討してください。これはクライアントを支援しますが、必要に応じてクライアントの自由度を高めます[1]。
    もちろん、主題は他の場所で詳細に議論されてきました[2、3]。

[1]もちろん、コードがさらに追加されますが、簡潔さを第一の関心事とする場合は、そもそもJavaを避けるべきでした。

[2] Joshua Bloch、Effective Java、アイテム16-18。

[3] http://www.codeproject.com/KB/ar ...


3

一般的な実装に抽象クラスを使用することに関する以前のコメントは、間違いなく注目に値します。まだ言及していない利点の1つは、インターフェースを使用すると、ユニットテストの目的でモックオブジェクトを実装するのがはるかに簡単になることです。Jason Cohenが説明したようにIPetとPetBaseを定義することで、物理データベースのオーバーヘッドなしに、さまざまなデータ条件を簡単に模擬できます(実際にテストする時が来るまで)。


3

基本クラスが何を意味するのか、およびこの場合に適用されることを知らない限り、基本クラスを使用しないでください。該当する場合はそれを使用し、そうでない場合はインターフェースを使用してください。しかし、小さなインターフェースに関する答えに注意してください。

Public InheritanceはOODで過剰に使用されており、ほとんどの開発者が気づいている、または喜んで従うよりもはるかに多くのことを表現しています。リスコフの代用性原理を参照してください

つまり、AがBである場合、Aは公開するすべてのメソッドについて、B以下を必要とし、B以上を配信します。


3

覚えておくべきもう1つのオプションは、「has-a」関係を使用することです。別名は「の観点から」または「構成」で実装されます。これは、「is-a」継承を使用するよりも、よりクリーンで柔軟な方法で構造化できる場合があります。

犬と猫の両方がペットを「飼っている」と言うことは論理的にそれほど意味がないかもしれませんが、一般的な多重継承の落とし穴を回避します。

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

はい、この例は、コードの重複が多く、この方法で行うことに優雅さが欠けていることを示しています。しかし、これはDogとCatをPetクラスから切り離すのに役立ち(そのDogとCatはPetのプライベートメンバーにアクセスできないため)、犬と猫が他のものから継承する余地を残すことも認めるべきです- -おそらく哺乳類クラス。

プライベートアクセスが不要で、一般的なペット参照/ポインターを使用して犬と猫を参照する必要がない場合は、構成が望ましいです。インターフェースは、その一般的な参照機能を提供し、コードの冗長性を削減するのに役立ちますが、構成が不十分な場合にも難読化する可能性があります。継承は、プライベートメンバーアクセスが必要な場合に役立ちます。継承を使用すると、DogクラスとCatクラスをPetクラスに高度に結合することができます。

継承、構成、およびインターフェースの間には、常に正しい方法は1つではありません。3つのオプションすべてをどのように調和させて使用できるかを検討することが役立ちます。3つのうち、継承は通常、使用頻度が最も低いオプションです。


3

概念的には、インターフェイスは、オブジェクトが提供する一連のメソッドを正式および半正式に定義するために使用されます。形式的にはメソッド名とシグネチャのセットを意味し、半形式的にはこれらのメソッドに関連付けられた人間が読めるドキュメントを意味します。

インターフェースはAPIの説明に過ぎず(結局のところ、APIはアプリケーションプログラミングインターフェースを意味します)、インターフェースを実装することはできません。また、インターフェースを使用または実行することはできません。それらは、オブジェクトとどのように対話する必要があるかについての契約を明示的にするだけです。

クラスは実装を提供し、ゼロ、1つ以上のインターフェースを実装することを宣言できます。クラスの場合継承されることを意図され、大会は「基本」とクラス名の接頭辞です。

基本クラス抽象基本クラス(ABC)には違いがあります。ABCはインターフェースと実装を混ぜ合わせます。コンピュータプログラミングの外側の抽象は「要約」、つまり「抽象==インターフェース」を意味します。次に、抽象基本クラスは、インターフェースと、継承を意図した空の部分的または完全な実装の両方を記述できます。

使用する際に意見のインターフェイス抽象基底クラスだけ対のクラスは、あなたが開発しているものの両方に基づいて、乱暴に変化することが起こっている、とあなたはで開発されている言語です。インターフェースは、多くの場合、JavaやC#などの静的型付けの言語にのみ関連しているが、動的に型付けされた言語には、インターフェイス抽象基本クラス含めることもできます。Pythonでは、例えば、区別は、それが宣言クラス、間明らかな実装インタフェースを、のインスタンスであるオブジェクト、クラス、と言われている提供しますことインターフェースます。動的言語では、同じクラスのインスタンスである2つのオブジェクトが、完全に異なるインターフェースを提供することを宣言できる場合があります。Pythonでは、これはオブジェクト属性に対してのみ可能ですが、メソッドはクラスのすべてのオブジェクト間で状態を共有します。ただし、Rubyでは、オブジェクトにインスタンスごとのメソッドを含めることができるため、同じクラスの 2つのオブジェクト間のインターフェースは、プログラマーが望むだけ変化する可能性があります(ただし、Rubyにはインターフェースを宣言する明示的な方法がありません)。

動的言語では、オブジェクトへのインターフェースは、オブジェクトをイントロスペクトしてそれが提供するメソッドを尋ねること(跳躍の前に見る)、またはできればオブジェクトで目的のインターフェースの使用を試み、オブジェクトがそのインターフェースは提供していません(許可よりも許しを求める方が簡単です)。これは、2つのインターフェースが同じメソッド名を持っているが意味的に異なる「誤検知」を引き起こす可能性があります。ただし、コードのすべての可能な使用を予測するために事前に過剰に指定する必要がないため、トレードオフはコードがより柔軟であることです。


2

要件によって異なります。IPetが十分に単純であれば、私はそれを実装したいと思います。そうでなければ、PetBaseが実装したくない機能を大量に実装しているなら、それを実行してください。

基本クラスを実装することの欠点は、 overridenew既存のメソッド(または)。これにより、それらは仮想メソッドになります。つまり、オブジェクトインスタンスの使用方法に注意する必要があります。

最後に、.NETの単一継承は私を殺します。単純な例:ユーザーコントロールを作成しているため、を継承するとしUserControlます。しかし、今、あなたも継承から締め出されていますPetBase。これによりPetBase、代わりにクラスメンバーを作成するなど、再編成が必要になります。


2

私は通常、必要になるまで実装しません。私は抽象クラスよりもインターフェースを好んでいます。継承するクラスの一部に共通の動作がある場合は、それを上に移動して抽象基本クラスを作成します。どちらも本質的に同じ目的を果たしているため、両方の必要性はないと思います。両方を使用することは、ソリューションが過剰に設計されているという悪いコードの匂いです。


2

C#に関しては、ある意味でインターフェースと抽象クラスは交換可能です。ただし、違いは次のとおりです。i)インターフェイスはコードを実装できません。ii)このため、インターフェイスはスタックをさらに呼び出してサブクラスにすることはできません。iii)クラスに継承できるのは抽象クラスのみですが、クラスには複数のインターフェースを実装できます。


2

defにより、インターフェースは他のコードと通信するためのレイヤーを提供します。クラスのすべてのパブリックプロパティとメソッドは、デフォルトで暗黙的なインターフェイスを実装しています。インターフェイスをロールとして定義することもできます。クラスがそのロールを実行する必要がある場合は常に、それを実装する必要があり、それを実装するクラスに応じて異なる形式の実装を提供します。したがって、インターフェースについて話すときはポリモーフィズムについて話し、基本クラスについて話すときは継承について話します。おっとの2つの概念!!!


2

Interface> Abstract> Concreteのパターンが次のユースケースで機能することがわかりました。

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

抽象クラスは、具象クラスのデフォルトの共有属性を定義しますが、インターフェースを実施します。例えば:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

さて、すべての哺乳類には髪と乳首があるので(AFAIK、私は動物学者ではありません)、これを抽象基本クラスにまとめることができます

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

そして、具体的なクラスは、まっすぐ歩くことを定義するだけです。

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

この設計は、多くの具象クラスがあり、インターフェイスにプログラミングするためだけにボイラープレートを維持したくない場合に適しています。新しいメソッドがインターフェースに追加された場合、結果のクラスのすべてが壊れるので、インターフェースアプローチの利点が得られます。

この場合、アブストラクトは具体的にすることもできます。ただし、抽象的な指定は、このパターンが採用されていることを強調するのに役立ちます。


1

基本クラスの継承者は「is a」関係を持つ必要があります。インターフェイスは「実装」関係を表します。したがって、継承者がis関係を維持する場合にのみ、基本クラスを使用してください。


1

インターフェースを使用して、無関係なクラスのACROSSファミリーのコントラクトを適用します。たとえば、コレクションを表すクラスに共通のアクセスメソッドがあり、根本的に異なるデータが含まれている場合があります。つまり、1つのクラスはクエリの結果セットを表し、もう1つのクラスはギャラリーの画像を表す場合があります。また、複数のインターフェースを実装できるため、クラスの機能をブレンド(および指定)できます。

クラスが共通の関係を持ち、したがって類似した構造的および行動的シグネチャを持つ場合、つまり、自動車、バイク、トラック、およびSUVがすべてのタイプの道路車両であり、最高速度である場合、継承を使用します。

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