いつインターフェイスを使用する必要がありますか?基本クラスはいつ使用しますか?
実際にメソッドの基本実装を定義したくない場合、それは常にインターフェイスでなければなりませんか?
犬と猫のクラスがあれば。なぜ私はPetBaseの代わりにIPetを実装したいのですか?私はIShedsやIBarks(IMakesNoise?)のインターフェースを持つことは理解できます。なぜなら、それらはペットベースでペットに置くことができるからですが、一般的なペットにはどちらを使うのか分かりません。
DogとCatクラスの例を取り上げ、C#を使って説明しましょう:
犬と猫の両方が動物、特に4匹の哺乳類です(動物はあまりにも一般的です)。両方とも、抽象クラス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つの抽象クラス(継承されたほとんどの静的型オブジェクト指向言語(C ++)を例外に含む)では継承できますが、複数のインタフェースを実装できるため、厳密にオブジェクトを構築できます要求に応じ基礎。
まあ、ジョシュ・ブロッホ効果的なJava 2d:
いくつかの主なポイント:
既存のクラスを簡単に改装して新しい インタフェース。あなたがしなければならないのは、 彼らがまだいないなら、必要な方法 存在し、implements節を追加する クラス宣言。
インターフェイスはミックスインを定義するのに理想的です。ゆるやかに言えば、 mixinはクラスができる型です その "プライマリ" それが提供することを宣言するためにタイプする いくつかのオプションの動作。例えば、 Comparableはmixinインターフェイスです クラスは、 インスタンスは、 相互に匹敵する他のオブジェクト。
インタフェースは非階層型の構築を可能にする フレームワーク。タイプの階層は いくつかのものを整理するのに最適です 他のものはきれいに落ちない 厳格な階層。
インターフェイスにより、安全で強力な機能拡張が可能経由で ラップ・クラス・イディオム。使用する場合 タイプを定義するための抽象クラス 追加したいプログラマーに任せる 代わりのものではなく機能性 継承を使う。
さらに、あなたは美徳を組み合わせることができます のインタフェースと抽象クラスの 抽象的な骨格を提供する それぞれと一緒に行く実装クラス あなたがエクスポートする重要なインターフェース。
一方、インターフェースは非常に進化しにくいです。インターフェイスにメソッドを追加すると、そのメソッドの実装がすべて破棄されます。
PS:本を購入する。それはもっと詳細です。
現代的なスタイルはIPetを定義することですそしてPetBase。
このインターフェースの利点は、他のコードが他の実行可能コードとの関係なく使用できることです。完全に「きれい」。また、インタフェースを混在させることもできます。
しかし、基本クラスは単純な実装や一般的なユーティリティに便利です。したがって、時間とコードを節約するために抽象基本クラスも提供してください。
インタフェースと基本クラスは、2つの異なる形式の関係を表します。
継承(基本クラス)は「is-a」関係を表します。例えば。犬や猫 "はペットです。この関係は、常に(単一の)目的クラスの"単一責任の原則")。
インターフェイス一方、追加機能クラスの私はそれを "と"の関係、つまり "Foo
使い捨てです。 "IDisposable
C#のインターフェイス。
一般に、抽象クラスよりもインターフェイスを優先するべきです。抽象クラスを使用する理由の1つは、具体的なクラス間で共通の実装がある場合です。もちろん、インタフェース(IPet)を宣言し、そのインタフェースを抽象クラス(PetBase)で実装する必要があります。小さくて別個のインタフェースを使用すると、倍数を使用して柔軟性をさらに高めることができます。インターフェイスを使用すると、境界全体で最大限の柔軟性と型の移植性が実現します。境界を越えて参照を渡すときは、常に具体的な型ではなくインターフェイスを渡します。これにより、受信側は具体的な実装を決定し、最大の柔軟性を提供します。これは、TDD / BDD方式でプログラミングする場合には絶対に当てはまります。
"継承はサブクラスを親の実装の詳細に晒すため、継承がカプセル化を破ると言われることがよくあります。私はこれが正しいと信じています。
これはかなり.NET固有のものですが、Framework Design Guidelinesの本では、一般的なクラスでは進化するフレームワークに柔軟性があると主張しています。インタフェースが出荷されると、そのインタフェースを使用したコードを破棄することなく、インタフェースを変更する機会はありません。ただし、クラスを使用すると、そのクラスにリンクするコードを変更することはできません。新しい機能を追加するなど、適切な変更を行う限り、コードを拡張して展開することができます。
Krzysztof Cwalinaは81ページにこう書いています:
3つのバージョンの.NET Frameworkの中で、私はこのガイドラインについて、私たちのチームのかなりの開発者と話しました。当初ガイドラインに同意しなかった人を含む多くの人は、APIとしてインタフェースを提供したことを後悔していると述べています。私は誰かがクラスを出荷したことを後悔した1件の事例についても聞いていない。
それは確かにインターフェイスのための場所があると言われています。一般的なガイドラインとして、インタフェースを実装する方法の例として何もない場合は、インタフェースの抽象基本クラス実装を常に提供してください。最高の場合、基本クラスは多くの作業を節約します。
フアン、
私はインターフェイスをクラスを特徴付ける方法と考えるのが好きです。特定の犬種クラス、例えばYorkshireTerrierは、親犬クラスの子孫であるかもしれないが、IFurry、IStubby、およびIYippieDogも実装されている。したがって、クラスはクラスが何であるかを定義しますが、インタフェースはそれについて私たちに指示します。
これの利点は、例えば、すべてのIYippieDogを集めてOceanコレクションに投げることができるということです。だから今私は、特定のオブジェクトのセットに到達し、クラスをあまりにも綿密に調べることなく、私が見ている基準を満たすものを見つけることができます。
私はインターフェイスが本当にクラスのパブリックな動作のサブセットを定義する必要があることを知っています。それが実装しているすべてのクラスのすべてのpublic動作を定義している場合、通常は存在する必要はありません。彼らは私に有用な何かを教えていない。
この考えは、すべてのクラスにインターフェイスが必要であり、インターフェイスにコードする必要があるという考え方に反するものです。それは問題ありませんが、クラスへの1対1のインターフェースが多くなり、混乱させます。私はそのアイデアは実際には何の費用もかからないことを理解しています。今では物事を容易に入れ替えることができます。しかし、私はめったにそれをしていないことがわかります。ほとんどの場合、既存のクラスを変更しているだけで、そのクラスのパブリックインターフェイスが変更する必要がある場合は、2つの場所で変更する必要がある場合を除き、常に同じ問題が発生します。
あなたが私のように考えるなら、あなたは間違いなく猫と犬がIPettableだと言うでしょう。両方に一致するのは特徴付けです。
他の部分は、彼らは同じ基本クラスを持っている必要がありますか?問題は、彼らが広く同じように扱われる必要があるということです。確かに彼らは両方の動物ですが、それらを一緒に使用する方法に合っています。
すべての動物クラスを集めて、それらを私の箱のコンテナに入れたいとします。
あるいは彼らは哺乳類である必要がありますか?おそらく、ある種の動物搾り工場が必要でしょうか?
彼らはまったく一緒にリンクする必要がありますか?彼らは両方がIPettableであることを知るだけで十分ですか?
クラスを1つだけ必要とするときに、クラス階層全体を派生させたいという気持ちがしばしば感じられます。私はいつかそれを必要とするかもしれないと予想して、通常私はやりません。私がしても、私はそれを直すために多くのことをしなければならないのが普通です。それは、私が作っているファーストクラスは犬ではないから、私は幸運ではない、それはカモノハシです。クラス全体の階層構造は奇妙なケースに基づいており、無駄なコードがたくさんあります。
ある時点で、すべての猫がIPettableであるとは限りません。今度は、そのインターフェースをフィットするすべての派生クラスに移動できます。突然の猫のすべてがもはやPettableBaseから派生したものではないことははるかに少ない変化であることがわかります。
ここでは、インターフェイスと基本クラスの基本的で単純な定義があります:
乾杯
可能であれば、継承の代わりに合成を使用することをお勧めします。インタフェースを使用しますが、基本実装にはメンバオブジェクトを使用します。こうすることで、特定の方法で動作するようにオブジェクトを構築するファクトリを定義できます。この振る舞いを変更したい場合、異なるタイプのサブオブジェクトを作成する新しいファクトリメソッド(または抽象ファクトリ)を作成します。
場合によっては、すべての変更可能な動作がヘルパーオブジェクトで定義されている場合は、プライマリオブジェクトにはインタフェースがまったく必要ないことがあります。
したがって、IPetやPetBaseではなく、IFurBehaviorパラメータを持つPetで終わる可能性があります。 IFurBehaviorパラメータは、PetFactoryのCreateDog()メソッドによって設定されます。 shed()メソッドのために呼び出されるのはこのパラメータです。
これを行うと、コードがはるかに柔軟になり、シンプルなオブジェクトのほとんどが非常に基本的なシステム全体の振る舞いを扱うことになります。
私は複数の継承言語でもこのパターンをお勧めします。
これでうまく説明されたJava Worldの記事
個人的には、インタフェースを使用する傾向があります。つまり、何かにアクセスする方法を指定するシステム設計の部分です。
1つ以上のインターフェースを実装するクラスを持つことは珍しくありません。
私が他の何かの基礎として使う抽象クラス。
以下は上記の記事からの抜粋ですJavaWorld.comの記事、著者Tony Sintes、04/20/01
インタフェースと抽象クラス
インタフェースと抽象クラスを選択することは、どちらの/または命題でもありません。デザインを変更する必要がある場合は、それをインターフェースにします。ただし、いくつかのデフォルト動作を提供する抽象クラスがあるかもしれません。抽象クラスは、アプリケーションフレームワーク内の優れた候補です。
抽象クラスではいくつかの振る舞いを定義できます。彼らはあなたのサブクラスが他のものを提供するよう強制します。たとえば、アプリケーションフレームワークを使用している場合、抽象クラスはイベントやメッセージ処理などのデフォルトサービスを提供します。これらのサービスでは、アプリケーションをアプリケーションフレームワークにプラグインすることができます。ただし、アプリケーションのみが実行できるアプリケーション固有の機能があります。このような機能には、多くの場合アプリケーションに依存する起動タスクやシャットダウンタスクが含まれる場合があります。したがって、その動作自体を定義するのではなく、抽象基本クラスは抽象シャットダウンと起動メソッドを宣言できます。基本クラスは、それらのメソッドが必要であることを知っていますが、抽象クラスは、クラスがそれらのアクションの実行方法を知らないことを認めます。それは行動を開始しなければならないことを知っているだけです。起動するときは、抽象クラスは起動メソッドを呼び出すことができます。基本クラスがこのメソッドを呼び出すと、Javaは子クラスによって定義されたメソッドを呼び出します。
多くの開発者は、抽象メソッドを定義するクラスもそのメソッドを呼び出すことができることを忘れています。抽象クラスは、計画された継承階層を作成する優れた方法です。クラス階層の非リーフクラスにも適しています。
クラス対インタフェース
インターフェイスの面ですべてのクラスを定義すべきだと言う人もいますが、推薦は少し極端なようです。デザインの中の何かが頻繁に変わることがわかったら、私はインターフェイスを使用します。
たとえば、Strategyパターンを使用すると、新しいアルゴリズムやプロセスを使用するオブジェクトを変更せずに、新しいアルゴリズムやプロセスをプログラムにスワップできます。メディアプレーヤーは、CD、MP3、WAVファイルの再生方法を知っているかもしれません。もちろん、これらの再生アルゴリズムをプレーヤーにハードコードしたくない場合は、 AVIのような新しいフォーマットを追加するのが難しくなります。さらに、あなたのコードは無駄なケースステートメントで散らばります。また、傷害に侮辱を加えるためには、新しいアルゴリズムを追加するたびにそれらのcase文を更新する必要があります。全体として、これは非常にオブジェクト指向のプログラミング方法ではありません。
ストラテジーパターンを使用すると、アルゴリズムをオブジェクトの背後にカプセル化することができます。これを行うと、いつでも新しいメディアプラグインを提供することができます。プラグインクラスMediaStrategyを呼び出しましょう。そのオブジェクトにはplayStream(Stream s)という1つのメソッドがあります。したがって、新しいアルゴリズムを追加するには、アルゴリズムクラスを拡張するだけです。今、プログラムが新しいメディアタイプに遭遇すると、ストリームの再生をメディア戦略に委譲します。もちろん、必要なアルゴリズム戦略を正しくインスタンス化するには、配管工事が必要です。
これは、インターフェイスを使用するのに最適な場所です。 Strategyパターンを使用しました。このパターンは、デザイン内で変更される場所を明確に示しています。したがって、戦略をインターフェースとして定義する必要があります。オブジェクトに特定の型を持たせたい場合は、継承よりもインターフェイスを優先するのが一般的です。この場合、MediaStrategy。型識別のための継承に依存することは危険です。あなたを特定の継承階層にロックします。 Javaでは多重継承ができないため、有用な実装やより多くの型識別を提供するものを拡張することはできません。
また、OOで一掃されないように心がけてください(ブログを見る)必要な振る舞いに基づいてオブジェクトを常にモデル化します。必要な唯一の振る舞いが動物の一般的な名前と種であるアプリを設計する場合、数百万の代わりに名前のプロパティを持つAnimalクラスが必要です世界のすべての可能な動物のためのクラス。
私は大雑把なルールを持っています
機能性:すべての部分で異なる可能性が高い:インタフェース。
データおよび機能性、部品はほとんど同じで、部品は異なります。抽象クラス。
わずかな変更だけで拡張した場合、実際に動作するデータと機能:普通(具体的)クラス
データおよび機能性、計画変更なし:final修飾子を持つ普通(具象)クラス。
データ、およびおそらく機能性:読み取り専用:enumメンバー。
これは非常に粗くて準備ができており、厳密に定義されているわけではありませんが、すべてが読み取り専用ファイルのように固定されている列挙型に変更されることが意図されているインタフェースからのスペクトルがあります。
インタフェースは小さくする必要があります。本当に小さい。実際にオブジェクトを分解しているのであれば、インターフェースにはおそらくいくつかの非常に具体的なメソッドとプロパティしか含まれていないでしょう。
抽象クラスはショートカットです。 PetBaseのすべての派生物があなたが一度コード化してやりなおすことができることはありますか?はいの場合、抽象クラスの時間です。
抽象クラスも制限されています。それらはあなたに子オブジェクトを生成するためのすばらしいショートカットを提供しますが、任意のオブジェクトは1つの抽象クラスを実装することしかできません。多くの場合、抽象クラスの制限があることがわかります。そのため、私は多くのインタフェースを使用しています。
抽象クラスにはいくつかのインタフェースが含まれています。あなたのPetBase抽象クラスは、IPet(ペットは所有者を持っています)とIDigestion(ペットは食べる、あるいは少なくとも食べなければなりません)を実装することができます。しかし、PetBaseはおそらくすべてのペットが哺乳類ではなく、すべての哺乳動物がペットであるわけではないので、IMammalを実装していないでしょう。 PetBaseを拡張し、IMammalを追加するMammalPetBaseを追加することができます。 FishBaseにはPetBaseがあり、IFishを追加することができます。 IFishはインターフェイスとしてISwimとIUnderwaterBreatherを持っています。
はい、私の例は単純な例では広範囲に複雑ですが、それはインタフェースと抽象クラスがどのように連携するかについての素晴らしい点の一部です。
インターフェイスを介した基本クラスのケースは、サブ.NET .NETコーディングガイドライン:
ベースクラスとインタフェースインターフェイスタイプは部分的です 値の説明、潜在的に 多くのオブジェクト型でサポートされています。つかいます インタフェースの代わりに基本クラス いつでも可能なとき。バージョン管理から 視点、クラスはより柔軟です インターフェイスよりも。クラスでは、 船のバージョン1.0とバージョン クラスに新しいメソッドを追加します。この方法が抽象的でない限り、 既存の派生クラスはいずれも継続する 変更しないで機能する。
インターフェイスはサポートしていないため 実装の継承、 クラスに適用されるパターンは インタフェースには適用されません。追加する メソッドと同等のもの ベースに抽象メソッドを追加する クラス;クラスを実装するクラス インターフェイスはクラスが壊れてしまいます 新しいメソッドを実装していません。 インターフェイスは、 以下の状況:
- 関係のないいくつかのクラスがプロトコルをサポートしたいと考えています。
- これらのクラスは既にベースクラスを確立しています(for 例えば、 いくつかはユーザーインターフェイス(UI)コントロール、 いくつかはXML Webサービスです)。
- 集約は適切でないか、または実行可能ではない。他のすべての 状況、 クラス継承はより良いモデルです。
重要な違いの1つは、継承できることだけです1基本クラスを実装することはできますたくさんのインターフェイス。あなたは基本クラスを使いたいだけです絶対に確かな別の基底クラスも継承する必要はありません。さらに、インターフェイスが大きくなっている場合は、独立した機能を定義するいくつかの論理的な要素に分解してみる必要があります。これは、クラスがすべてを実装できないというルールがないためです。それをグループ化するためにそれらをすべて継承する)。
ソース:http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C#は、過去14年間に成熟し進化した素晴らしい言語です。成熟した言語は私たちが自由に使える言語機能を豊富に提供しているため、これは私たちの開発者にとっては素晴らしいことです。
しかし、多くの力が大きな責任となります。これらの機能のいくつかは誤用されることがあります。また、ある機能を別の機能に使用する理由を理解することが難しい場合もあります。長年にわたり、多くの開発者が苦労しているのは、インターフェイスを使用するか、抽象クラスを使用するかを選択するときです。どちらにも長所と短所があり、それぞれを使用する正しい時間と場所があります。しかし、我々はどのように決定するか???
どちらもタイプ間の共通機能の再利用を可能にします。最も明白な相違点は、インタフェースでは機能が実装されていないことです。抽象クラスでは、「基本」または「デフォルト」の動作を実装し、必要に応じてこのデフォルトの動作をクラスの派生型で「オーバーライド」することができます。
これはすべて善良であり、コードの再利用性が高く、ソフトウェア開発のDRY(Do not Repeat Yourself)原則を遵守しています。抽象クラスは、 "is a"の関係を持つときに使用するのに適しています。
例:ゴールデンレトリーバーは「犬のタイプ」です。プードルもそうです。彼らは両方の犬ができるように、両方の樹皮することができます。しかし、あなたは、プードルパークが "デフォルト"の犬の樹皮と大きく異なっていると言いたいかもしれません。そのため、次のように実装することが理にかなっています。
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抽象クラスを実装しているだけで、「デフォルト」(Bassクラス)Barkを無料で継承できます。
しかし、私はあなたがすでにそれを知っていたと確信しています。
抽象クラスに対してインターフェイスを選択したい、またはその逆を選択する理由を理解したいからです。あなたが抽象クラスを介してインターフェイスを選択したいかもしれない1つの理由は、デフォルトの実装を持っていない、またはしたくないときです。これは、通常、インタフェースを実装している型が "is a"関係では関連していないためです。実際には、それぞれのタイプが何かをしたり、何かを持つことができるということを除いて、彼らはまったく関連する必要はありません。
今、一体何が意味するのでしょうか?まあ、たとえば:人間はアヒルではない...アヒルは人間ではない。かなり明白。しかし、アヒルと人間の両方は、(人間が水泳レッスンに合格したことを考えれば)泳げる能力を持っています。また、アヒルは人間ではないため、逆もありますので、これは「is a」のリアクションではなく、「できる」という関係であり、インタフェースを使用してそのことを説明することができます。
// 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メソッドの振る舞いを独自のクラス内で処理できるようになります。
概要:
だから私の主な経験則は、クラス階層のための "デフォルト"の機能を実装したいと思っているとき、またはあなたが作業しているクラスやタイプを "a"関係(ex。 "犬のタイプ)。
一方、「関係はありません」ではなく、何かをしたり何かを持つ能力を共有するタイプ(例えば、ダックは人間ではありませんが、アヒルと人間のシェア"能力"を泳ぐ)。
抽象クラスとインタフェースの間の別の違いは、クラスが1対多のインタフェースを実装できることですが、クラスは1つの抽象クラス(またはそのクラスのいずれかのクラス)からのみ継承できるということです。はい、クラスをネストし、継承階層を持つことができます(多くのプログラムでは必要ですが)。派生クラス定義で2つのクラスを継承することはできません(このルールはC#に当てはまります。これらの言語のインターフェイスがないためにのみ)。
インターフェイスを使用してインターフェイス分離原則(Interface Separation Principle(ISP))を遵守する場合は、覚えておいてください。 ISPはクライアントが使用しない方法に頼らざるを得ないと述べています。このため、インタフェースは特定のタスクに焦点を当て、通常は非常に小さい(例:IDisposable、IComparable)。
もう一つのヒントは、機能の細かく簡潔なビットを開発する場合は、インターフェイスを使用することです。大規模な機能ユニットを設計する場合は、抽象クラスを使用します。
これはいくつかの人々のために物事をクリアすることを望む!
また、より良い例を考えたり、何かを指摘したりすることができれば、以下のコメントでそれをしてください!
最初にオブジェクト指向プログラミングについて学び始めたとき、共通の振る舞いを共有するために継承を使用するという簡単な、おそらくよくある間違いがありました。たとえその振る舞いがオブジェクトの性質に不可欠ではなかったとしてもです。
この特定の質問で多く使用されている例をさらに構築するために、ロットガールフレンド、車、ファジーブランケット... - 私は、この共通の動作を提供するPetableクラス、およびそれを継承するさまざまなクラスを持っていた可能性があります。
しかし、訴えかけることは、これらのオブジェクトの性質の一部ではありません。はるかに重要な概念があります。は彼らの本性に不可欠 - ガールフレンドは人、車は陸上車、猫は哺乳動物です...
ビヘイビアは最初にインタフェース(クラスのデフォルトインタフェースを含む)に割り当てられ、(a)より大きいクラスのサブセットである大きなグループのグループに共通する場合にのみ、ベースクラスに昇格する必要があります「猫」および「人」は「哺乳動物」のサブセットである。
このキャッチは、私が最初にやったよりもオブジェクト指向の設計を十分に理解した後で、通常はそれを考えなくても自動的に行います。したがって、「インターフェイスへのコードではなく、抽象クラスではない」という明白な真実は、誰もがそれを言うのを迷惑に思うのは難しいことです。そして、他の意味を読み取ろうとし始めます。
私が追加したいもう一つのことは、クラスが純粋に抽象的な - 抽象的でない、継承されていないメンバーやメソッドが子、親、クライアントに公開されていない場合、それはなぜクラスですか?ある場合にはインターフェースで、他の場合にはNullで置き換えることができます。
一般的な実装に抽象クラスを使用することについてのこれまでのコメントは確かに目立っています。私がまだ触れていない1つの利点は、インタフェースを使用することにより単体テストの目的で模擬オブジェクトを実装する方がはるかに簡単であることです。 Jason Cohenが述べたように、IPetとPetBaseを定義することで、物理的なデータベースのオーバーヘッドなしに(実際のものをテストする時間を決めるまで)簡単にさまざまなデータ条件を模擬することができます。
基本クラスが何を意味し、この場合に適用されるのでないかぎり、基本クラスを使用しないでください。それが適用される場合はそれを使用し、それ以外の場合はインターフェイスを使用します。しかし、小さなインタフェースについての答えに注意してください。
公開継承はOODで過度に使用されており、ほとんどの開発者が認識しているか、またはそれまで喜んで生きているよりもはるかに多くを表現しています。を参照してくださいリスコフ代理の原理
要するに、A "が" B "ならば、Aはそれ以上のものを必要とし、B以上のものは提供しない。
概念的には、インタフェースは、オブジェクトが提供する一連のメソッドを正式および準形式的に定義するために使用されます。形式的にはメソッド名とシグネチャのセットを意味し、それらのメソッドに関連する人間が判読可能なドキュメントを半正式に意味します。インタフェースはAPIの記述でしかありません(結局、APIはApplication Programmerインタフェース)、それらは実装を含むことはできませんし、Interfaceを使用したり実行したりすることはできません。彼らはあなたがオブジェクトとやりとりする方法の契約を明示的にするだけです。
クラスは実装を提供し、ゼロ、1つ以上のインタフェースを実装することを宣言できます。クラスが継承されることを意図している場合、規約はクラス名の前に "Base"を付けることです。
基本クラスと抽象基本クラス(ABC)の間には区別があります。 ABCはインターフェースと実装を混在させます。コンピュータプログラミング以外の抽象概念は、「要約」、すなわち「抽象==インタフェース」を意味する。抽象基本クラスは、継承されることを意図した空の、部分的なまたは完全な実装だけでなく、インタフェースの両方を記述することができます。
インタフェースと抽象基本クラスのどちらを使うのかについての議論は、あなたが開発している言語と開発している言語の両方に基づいて大きく変わるでしょう。インタフェースは、JavaやC#のような静的型付けされた言語動的に型付けされた言語は、インタフェースと抽象基本クラスを持つこともできます。たとえば、Pythonでは、Classとの区別が明確になります。Classは、道具Interface、およびClassのインスタンスであるオブジェクトであり、提供するそのインタフェース。動的言語では、同じクラスのインスタンスである2つのオブジェクトが完全に提供されていることを宣言できます異なるインターフェイス。 Pythonでは、これはオブジェクト属性に対してのみ可能であり、メソッドはクラスのすべてのオブジェクト間で共有された状態です。しかしRubyでは、オブジェクトはインスタンスごとのメソッドを持つことができるので、同じクラスの2つのオブジェクト間のインタフェースは、プログラマが望むほど大きく変わる可能性があります(ただし、Rubyにはインタフェースを宣言する明示的な方法はありません)。
動的言語では、オブジェクトへのイントロスペクションと、それが提供するメソッド(Look Before You Leap)のどちらかを尋ねることによって、あるいはオブジェクトで目的のインターフェイスを単に使用しようと試みることによって、オブジェクトへのインターフェイスを暗黙的に仮定しますそのインターフェイスを提供しません(許可よりも許してもらう方が簡単です)。これは、2つのインタフェースが同じメソッド名を持ち、意味的に異なる「誤検出」につながる可能性がありますが、トレードオフは、可能なすべての使用を予期するために先験的な指定を必要としないため、あなたのコードの。
別の選択肢として、「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はペットのプライベートメンバーにアクセスできません)。そして、DogとCatが何か他のものから継承する余地を残します。 - 可能な限り哺乳類のクラス。
プライベートアクセスが不要で、一般的なPet参照/ポインタを使用してDogとCatを参照する必要がない場合は、構成が好ましいインターフェイスは汎用的な参照機能を提供し、コードの冗長さを減らすのに役立ちますが、構成が不十分な場合は難読化することもできます。継承は、プライベートなアクセスが必要な場合に便利です。使用すると、犬と猫のクラスをペットクラスに強く結びつけることになります。これは支払うのに険しい費用です。
継承、合成、およびインターフェースの間には、常に正しい方法が1つもなく、3つのオプションがどのように調和して使用されるかを検討するのに役立ちます。 3つのうち、継承は通常、最も頻繁に使用されるべきオプションです。
抽象クラスよりもインタフェースを優先する
理由、 考慮すべき主なポイント[すでに2つはここに記載されています]:
[1]もちろん、もっとコードを追加しますが、簡潔さを重視するなら、おそらく最初にJavaを避けるべきです!
[2] Joshua Bloch、Effective Java、項目16-18。
それはあなたの要求に依存します。 IPetが十分に単純ならば、私はそれを実装することを好むでしょう。そうでなければ、PetBaseが大量の機能を実装していて、複製したくない場合は、それを持ってください。
基本クラスを実装することの欠点は、override
(またはnew
)既存の方法。これは仮想メソッドになります。つまり、オブジェクトインスタンスの使用方法に注意する必要があります。
最後に、.NETの単一の継承が私を殺します。素朴な例:ユーザーコントロールを作成しているとしましょうUserControl
。しかし、今、あなたは相続することもできないPetBase
。これは、あなたがPetBase
代わりに、クラスメンバー。
@Joel:いくつかの言語(例えばC ++)では多重継承が可能です。
私が必要とするまで、私は通常どちらかを実装しません。私は抽象クラスよりもインタフェースを優先します。なぜなら、それは少し柔軟性を与えるからです。継承クラスのいくつかに共通の動作がある場合、私はそれを上げて抽象基本クラスを作成します。彼らは本質的に同じ目的のサーバであり、その両方が解決策が過度に設計されているという悪いコードの匂い(imho)であるため、私は両方の必要性を見ません。
C#に関して、ある意味では、インタフェースと抽象クラスは互換性があります。しかし、違いは次のとおりです。i)インターフェースはコードを実装できません。 ii)これにより、インタフェースはスタックをさらに呼び出してサブクラス化することはできません。 iii)抽象クラスはクラスに継承することができるのみであるが、複数のインタフェースはクラスに実装することができる。
defによって、interfaceは他のコードと通信するための層を提供します。クラスのすべてのパブリックプロパティとメソッドは、デフォルトで暗黙的インターフェイスを実装しています。インタフェースをロールとして定義することもできます。どのクラスでもその役割を果たす必要がある場合、実装するクラスに応じてさまざまな実装形態を実装する必要があります。したがって、インタフェースについて話すとき、多態性について話しています。基本クラスについて話しているときは、継承について話しています。おっとの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)
}
このデザインは、具体的なクラスがたくさんある場合に便利です。また、インターフェイスにプログラミングするための定型表現を維持したくない場合もあります。新しいメソッドがインターフェイスに追加された場合、結果として得られるすべてのクラスが破損するため、インターフェイスアプローチの利点が得られます。
この場合、要約も同様に具体的になります。しかし、抽象的な指定は、このパターンが採用されていることを強調するのに役立ちます。
基本クラスの継承者は、 "is a"の関係を持つ必要があります。インターフェースは "インプリメント"関係を表します。 あなたの継承者がその関係を維持するときは、基本クラスのみを使用してください。