質問

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

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

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

役に立ちましたか?

解決

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);
}

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

Dog と Cat の定義は次のようになります。

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

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

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

他のヒント

さて、ジョシュ・ブロックは自分自身を次のように述べています。 効果的な Java 2D:

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

いくつかの主要なポイント:

  • 既存のクラスは、新しいインターフェイスを実装するために簡単に改造できます. 。必要な方法がまだ存在しない場合は、必要な方法を追加し、クラス宣言に実装条項を追加することです。

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

  • インターフェイスにより、非階層型フレームワークの構築が可能になります. 。タイプの階層はいくつかのものを整理するのに最適ですが、他のものは厳格な階層にきちんと落ちることはありません。

  • インターフェイスにより、安全で強力な機能拡張が可能になります クラスごとのイディオムを介して。抽象クラスを使用してタイプを定義する場合、継承を使用する以外に代替品なしで機能を追加したいプログラマーを残します。

さらに、抽象的な骨格実装クラスを提供して、エクスポートする各非自明なインターフェイスに合わせて、インターフェイスと抽象クラスの美徳を組み合わせることができます。

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

追伸:本を買ってください。かなり詳しくなりました。

現代のスタイルはIPetを定義することです そして ペットベース。

このインターフェイスの利点は、他のコードが他の実行可能コードと何の関係も持た​​ずにインターフェイスを使用できることです。完全に「きれい」。また、インターフェイスを混合できます。

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

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

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

インターフェース, 一方、 を表します 追加機能 クラスの。私はこれを「である」関係と呼びます。Foo 使い捨てです」、したがって、 IDisposable C#のインターフェース。

インターフェース

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

基本クラス

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

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

Gang of Four は著書の中で、「継承によってサブクラスがその親の実装の詳細にさらされるため、『継承はカプセル化を破壊する』とよく言われます」と述べています。私はこれが真実だと信じています。

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

クシシュトフ・クワリナは81ページでこう述べています。

.NET Framework の 3 つのバージョンにわたって、私はチームのかなりの数の開発者とこのガイドラインについて話し合いました。最初にガイドラインに反対した人も含め、その多くは、API をインターフェースとして出荷したことを後悔していると述べています。授業を出荷したことを後悔したという話は一件も聞いたことがありません。

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

ファン、

私はインターフェイスをクラスを特徴付ける方法として考えるのが好きです。特定の犬種クラス (ヨークシャー テリアなど) は、親犬クラスの子孫である可能性がありますが、IFurry、IStubby、および IYippieDog も実装されています。したがって、クラスはクラスが何であるかを定義しますが、インターフェイスはそれについての情報を提供します。

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

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

ただし、この考えは、すべてのクラスにインターフェイスが必要であり、そのインターフェイスに対してコードを記述する必要があるという考えに反します。それは問題ありませんが、クラスへの 1 対 1 のインターフェイスが多数存在することになり、状況が混乱してしまいます。実際にコストがかからず、簡単に物事を交換できるようになったという考えは理解しています。しかし、私はそれをすることがほとんどないことに気づきました。ほとんどの場合、既存のクラスを適切に変更するだけで、そのクラスのパブリック インターフェイスを変更する必要がある場合に常に発生するのとまったく同じ問題が発生します。ただし、2 か所で変更する必要がある点が異なります。

したがって、あなたが私と同じように考えるなら、猫と犬は愛らしいと間違いなく言うでしょう。両者にマッチしたキャラクター描写です。

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

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

それとも哺乳類である必要があるのでしょうか?おそらく、何らかの異種動物搾乳工場が必要なのではないでしょうか?

そもそもそれらをリンクする必要があるのでしょうか?両方とも IPettable であることを知るだけで十分ですか?

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

また、ある時点で、すべての猫が愛撫できるわけではないことに気づくかもしれません (毛のない猫など)。これで、そのインターフェイスを、適合するすべての派生クラスに移動できるようになりました。それほど重大な変更ではありませんが、突然 Cats が PettableBase から派生しなくなることがわかります。

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

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

乾杯

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

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

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

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

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

これでよく説明されました Java World の記事

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

クラスに 1 つ以上のインターフェイスを実装することは珍しいことではありません。

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

以下は上記記事からの抜粋です JavaWorld.com の記事、著者 Tony Sintes、2001 年 4 月 20 日


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

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

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

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

クラス vs.インターフェース

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

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

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

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

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

私には大まかな経験則があります

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

データと機能の部分はほとんど同じですが、異なる部分があります。 抽象クラス。

データと機能は、わずかな変更を加えて拡張するだけで実際に動作します。 通常の(具体的な)ク​​ラス

データと機能、変更の予定なし: Final 修飾子を持つ通常の (具体的な) クラス。

データ、そして場合によっては機能:読み取り専用: enum メンバー。

これは非常に大まかで準備ができており、厳密に定義されていませんが、すべてを変更することを目的としたインターフェイスから、読み取り専用ファイルのようにすべてが固定された列挙型までの範囲があります。

インターフェイスは小さくする必要があります。本当に小さいです。オブジェクトを実際に分割している場合、インターフェイスにはおそらく、非常に特殊なメソッドとプロパティがいくつか含まれるだけになります。

抽象クラスはショートカットです。PetBase のすべての派生製品に共通して、一度コーディングすれば完了するものはありますか?「はい」の場合は、抽象クラスを使用します。

抽象クラスにも制限があります。これらは子オブジェクトを作成するための優れた近道を提供しますが、特定のオブジェクトは 1 つの抽象クラスしか実装できません。多くの場合、これが抽象クラスの制限であることがわかり、これが私が多くのインターフェイスを使用する理由です。

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

はい、私の例は単純な例にしては非常に複雑すぎますが、それはインターフェイスと抽象クラスがどのように連携するかに関する優れた点の一部です。

インターフェイス上の基本クラスのケースについては、Submain .NET コーディング ガイドラインで詳しく説明されています。

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

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

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

ソース: http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-Should-you-use/

C# は、過去 14 年間にわたって成熟し、進化してきた素晴らしい言語です。成熟した言語では、自由に使える言語機能が豊富に提供されるため、これは開発者にとって素晴らしいことです。

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

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

これはすべて良いことであり、コードの大幅な再利用を可能にし、ソフトウェア開発の DRY (Don't Reply 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 つは、デフォルトの実装がない場合、またはデフォルトの実装を防止したい場合です。これは通常、インターフェイスを実装している型が「である」関係で関連付けられていないことが原因です。実際には、それぞれのタイプが何かをするか何かを「できる」または「能力」を持っているという事実を除いて、それらはまったく関連している必要はありません。

さて、それは一体何を意味するのでしょうか?たとえば、次のようにします。人間はアヒルではありません…そしてアヒルは人間ではありません。かなり明白。しかし、アヒルも人間も泳ぐ「能力」を持っています (人間は 1 年生で水泳の授業に合格したことを考えると :) )。また、アヒルは人間ではないため、またはその逆であるため、これは「である」という関係ではなく、「できる」という関係であり、インターフェイスを使用してそれを示すことができます。

// 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 メソッドを呼び出すことができ、そのオブジェクトはその型に基づいて実行時にどのような動作を行うかを認識することだけです。

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

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

まとめ:

したがって、私の主な経験則は、クラス階層の「デフォルト」機能を実装したい場合、または作業しているクラスや型が「である」関係を共有する場合(例:プードルは犬「です」)。

一方、「である」関係はないが、何かを行うか何かを持つ「能力」を共有するタイプがある場合には、インターフェイスを使用します (例:アヒルは人間「ではない」。ただし、アヒルと人間は泳ぐという「能力」を共有しています)。

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

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

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

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

また、より良い例を思いついた場合、または何か指摘したい場合は、以下のコメント欄に記入してください。

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

私が初めてオブジェクト指向プログラミングについて学び始めたとき、共通の動作を共有するために継承を使用するという、おそらくよくある簡単な間違いを犯しました (その動作がオブジェクトの性質にとって本質的ではない場合でも)。

この特定の質問でよく使用される例をさらに発展させるには、次のとおりです。 たくさん ガールフレンド、車、毛羽立った毛布など、愛でるものの数々。- したがって、この共通の動作を提供する Petable クラスと、それを継承するさまざまなクラスがあった可能性があります。

ただし、撫でられるという性質は、これらのオブジェクトの性質の一部ではありません。もっと重要な概念があります。 彼らの性質にとって不可欠なものです - ガールフレンドは人間であり、車は陸上車両であり、猫は哺乳類です...

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

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

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

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

理論的根拠、[ここですでに言及されている2つ]を考慮する主なポイントは次のとおりです。

  • クラスは複数のインターフェイスを実装できるため、インターフェイスはより柔軟です。Javaには複数の継承がないため、抽象クラスを使用すると、ユーザーが他のクラス階層を使用することができなくなります。 一般に、デフォルトの実装や状態がない場合は、インターフェイスを好みます。 Javaコレクションは、この(マップ、セットなど)の良い例を提供します。
  • 抽象クラスには、より良い順方向の互換性を可能にするという利点があります。クライアントがインターフェイスを使用すると、それを変更することはできません。抽象クラスを使用している場合でも、既存のコードを破らずに動作を追加できます。 互換性が懸念事項である場合は、抽象クラスの使用を検討してください。
  • デフォルトの実装または内部状態がある場合でも、インターフェースとその抽象実装の提供を検討してください。。これはクライアントを支援しますが、必要に応じてより大きな自由を許可します[1]。
    もちろん、被験者は他の場所で詳細に議論されています[2,3]。

[1] もちろん、より多くのコードが追加されますが、簡潔さを最重視するのであれば、おそらく最初から Java を避けるべきでした。

[2] Joshua Bloch、『Effective Java』、項目 16 ~ 18。

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

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

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

パブリック継承は OOD で過度に使用されており、ほとんどの開発者が認識している、または喜んでそれに応えようとしているよりもはるかに多くのことを表現します。を参照してください。 リスコフの代替可能性の原則

つまり、A が B である場合、A は公開するすべてのメソッドについて、B を超えるものを必要とせず、B を超えるものを提供します。

概念的には、インターフェイスは、オブジェクトが提供する一連のメソッドを正式および半正式に定義するために使用されます。形式的にはメソッド名とシグネチャのセットを意味し、準形式的にはそれらのメソッドに関連付けられた人間が判読できるドキュメントを意味します。インターフェイスは API の説明にすぎません (結局のところ、API は Application Programmer の略です) インターフェース)、実装を含めることはできず、インターフェイスを使用または実行することもできません。これらは、オブジェクトと対話する方法についての規約のみを明示します。

クラスは実装を提供し、ゼロ、1 つ、または複数のインターフェイスを実装することを宣言できます。クラスを継承する場合、慣例としてクラス名の先頭に「Base」を付けます。

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

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

動的言語では、オブジェクトへのインターフェイスは、オブジェクトをイントロスペクトしてそのオブジェクトが提供するメソッドを尋ねること (Look Before You Leap) によって、またはできれば単純にオブジェクト上で目的のインターフェイスを使用しようとして、オブジェクトが適切でない場合は例外をキャッチすることによって、暗黙的に想定されます。はそのインターフェイスを提供しません(許可よりも許しを求める方が簡単です)。これにより、2 つのインターフェイスが同じメソッド名を持つが意味的に異なるという「誤検知」が発生する可能性がありますが、その代わりに、考えられるすべての使用を予測するために事前に過剰に指定する必要がないため、コードがより柔軟になります。コードの。

留意すべきもう1つのオプションは、「Has-A」関係を使用することです。別名「構成」の観点から実装されます。時には、これは「is-a」継承を使用するよりも、物事を構築するためのよりクリーンで柔軟な方法です。

Dog と Cat の両方がペットを「持っている」と言うのは、論​​理的にはあまり意味がありませんが、よくある多重継承の落とし穴を回避できます。

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 参照/ポインタを使用して Dog と Cat を参照する必要がない場合は、コンポジションが推奨されます。インターフェイスは汎用参照機能を提供し、コードの冗長性を軽減するのに役立ちますが、構成が不十分な場合は内容がわかりにくくなる可能性もあります。継承は、プライベート メンバー アクセスが必要な場合に便利です。継承を使用すると、Dog クラスと Cat クラスを Pet クラスに高度に結合することになりますが、これには多額のコストがかかります。

継承、合成、インターフェイスの間には、常に正しい方法が 1 つあるわけではありません。3 つのオプションすべてをどのように調和して使用できるかを検討するのに役立ちます。3 つのオプションのうち、継承は通常、最も使用する必要のないオプションです。

それはあなたの要件によって異なります。IPet が十分に単純であれば、それを実装したいと思います。それ以外の場合、PetBase に複製したくない機能が大量に実装されている場合は、そのまま使用してください。

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

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

@ジョエル:一部の言語 (C++ など) では多重継承が可能です。

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

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

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

次のユースケースでは、[インターフェイス] > [抽象] > [具体的] のパターンが機能することがわかりました。

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();
}

さて、すべての哺乳類には毛と乳首があるので (私の知る限り、私は動物学者ではありません)、これを抽象基本クラスに組み込むことができます。

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)
}

この設計は、具体的なクラスがたくさんあり、インターフェイスにプログラムするためだけに定型文を維持したくない場合に適しています。新しいメソッドがインターフェイスに追加された場合、結果として生成されるクラスはすべて壊れますが、それでもインターフェイス アプローチの利点は得られます。

この場合、要約は具体的なものでも構いません。ただし、抽象的な指定は、このパターンが使用されていることを強調するのに役立ちます。

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top