いつインターフェイスを使用し、いつ基本クラスを使用する必要がありますか?
メソッドの基本実装を実際に定義したくない場合は、常にインターフェイスにする必要がありますか?
犬と猫のクラスがある場合。PetBase の代わりに IPet を実装する必要があるのはなぜですか? ISheds または IBarks (IMakesNoise?) のインターフェイスを持つことは理解できます。これらはペットごとに配置できるためですが、一般的な Pet にどちらを使用すればよいかわかりません。
いつインターフェイスを使用し、いつ基本クラスを使用する必要がありますか?
メソッドの基本実装を実際に定義したくない場合は、常にインターフェイスにする必要がありますか?
犬と猫のクラスがある場合。PetBase の代わりに IPet を実装する必要があるのはなぜですか? ISheds または IBarks (IMakesNoise?) のインターフェイスを持つことは理解できます。これらはペットごとに配置できるためですが、一般的な Pet にどちらを使用すればよいかわかりません。
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++ を含む例外があります) が、複数のインターフェイスを実装できるため、必要に応じて厳密にオブジェクトを構築できます。
Josh Bloch は、Effective Java 2dで次のように述べています。
主なポイント:
既存のクラスを簡単に改造して、新しいインターフェースを実装できます。必要なメソッドがまだ存在しない場合は追加し、implements 句をクラス宣言に追加するだけです。
インターフェイスは mixin を定義するのに理想的です。大まかに言えば、ミックスインは、クラスが「プライマリ型」に加えて実装できる型であり、オプションの動作を提供することを宣言します。たとえば、 Comparable は、相互に比較可能な他のオブジェクトに対してインスタンスが順序付けられていることをクラスが宣言できるようにする mixin インターフェイスです。
インターフェイスにより、非階層型フレームワークの構築が可能になります。型階層は、いくつかのものを整理するのに最適ですが、他のものは厳格な階層にうまく収まりません。
インターフェイスは、ラッパー クラスのイディオムを介して、安全で強力な機能拡張を可能にします。抽象クラスを使用して型を定義する場合、機能を追加したいプログラマーは継承を使用する以外に選択肢がありません。
さらに、エクスポートする重要な各インターフェイスに対応する抽象骨格実装クラスを提供することで、インターフェイスと抽象クラスの利点を組み合わせることができます。
一方、インターフェースは進化が非常に困難です。インターフェイスにメソッドを追加すると、その実装がすべて壊れます。
PS.: 本を購入してください。それははるかに詳細です。
インターフェイスと基本クラスは、2 つの異なる形式の関係を表します。
継承(基本クラス) は「is-a」関係を表します。たとえば、犬や猫は「ペット」です。この関係は、常にクラスの (単一の)目的を表します ( 「単一責任の原則」と併せて)。
一方、インターフェースは、クラスの追加機能を表します。Foo
「は使い捨て」のように、「is」関係と呼びます。したがって、 IDisposable
C#のインターフェイスです。
現代的なスタイルは、IPetとPetBase を定義することです。
このインターフェースの利点は、他のコードが他の実行可能コードと何ら関係なく使用できることです。完全に「きれい」。また、インターフェイスを混在させることもできます。
ただし、基本クラスは単純な実装と一般的なユーティリティに役立ちます。したがって、時間とコードを節約するために、抽象基本クラスも提供します。
一般に、抽象クラスよりもインターフェイスを優先する必要があります。抽象クラスを使用する理由の 1 つは、具象クラス間で共通の実装がある場合です。もちろん、インターフェイス (IPet) を宣言し、抽象クラス (PetBase) でそのインターフェイスを実装する必要があります。小さくて異なるインターフェイスを使用すると、複数を使用して柔軟性をさらに向上させることができます。インターフェイスは、境界を越えた型の柔軟性と移植性を最大限に高めます。境界を越えて参照を渡す場合は、具体的な型ではなく、常にインターフェイスを渡します。これにより、受信側で具体的な実装を決定できるようになり、最大限の柔軟性が提供されます。これは、TDD/BDD 方式でプログラミングする場合に絶対に当てはまります。
The Gang of Four は著書の中で、「継承はサブクラスをその親の実装の詳細に公開するため、『継承はカプセル化を破る』とよく言われます」と述べています。私はこれが真実だと信じています。
これはかなり .NET 固有のものですが、Framework Design Guidelines ブックでは、進化するフレームワークでは一般にクラスがより柔軟になると主張しています。インターフェイスが出荷されると、そのインターフェイスを使用するコードを壊さずに変更する機会はありません。ただし、クラスを使用すると、それを変更しても、それにリンクしているコードを壊すことはできません。新しい機能の追加を含む適切な変更を行う限り、コードを拡張および進化させることができます。
Krzysztof Cwalina は 81 ページで次のように述べています。
.NET Framework の 3 つのバージョンの過程で、私はチームのかなりの数の開発者とこのガイドラインについて話し合ってきました。彼らの多くは、最初はガイドラインに同意しなかった人々を含め、API をインターフェースとして出荷したことを後悔していると述べています。クラスを出荷したことを後悔したというケースは、一度も聞いたことがありません。
そうは言っても、確かにインターフェースの場所があります。一般的なガイドラインとして、インターフェイスを実装する方法の例として、他に何もない場合でも、常にインターフェイスの抽象基本クラスの実装を提供します。最良の場合、基本クラスは多くの作業を節約します。
ファン、
私はインターフェイスをクラスを特徴付ける方法と考えるのが好きです。ヨークシャーテリアなどの特定の犬種クラスは、親犬クラスの子孫である可能性がありますが、IFurry、IStubby、および IYippieDog も実装しています。したがって、クラスはクラスが何であるかを定義しますが、インターフェースはそれについてのことを教えてくれます。
これの利点は、たとえば、すべての IYippieDog を集めて Ocean コレクションに入れることができることです。そのため、特定のオブジェクトのセットに到達し、クラスを詳しく調べなくても、探している基準を満たすオブジェクトを見つけることができます。
インターフェイスは、クラスの公開動作のサブセットを定義する必要があることがわかりました。実装するすべてのクラスのすべての公開動作を定義する場合、通常は存在する必要はありません。彼らは私に有益なことを何も教えてくれません。
ただし、この考えは、すべてのクラスにインターフェイスが必要であり、インターフェイスに対してコーディングする必要があるという考えに反しています。それは問題ありませんが、クラスへの 1 対 1 のインターフェースが多くなり、混乱を招きます。費用がかからず、簡単に交換できるという考えを理解しています。しかし、私はめったにそれをしないことに気づきました。ほとんどの場合、既存のクラスをその場で変更しているだけで、そのクラスのパブリック インターフェイスを変更する必要がある場合は、2 つの場所で変更する必要があることを除けば、常に同じ問題が発生します。
したがって、私のように考えるなら、間違いなく Cat と Dog は IPettable であると言うでしょう。その両方にマッチするキャラクター設定です。
ただし、これのもう1つの部分は、同じ基本クラスを持つべきかということです? 問題は、それらを広く同じものとして扱う必要があるかどうかです。確かにどちらも動物ですが、一緒に使用する方法に合っているでしょうか。
Animal クラスをすべて集めて、Ark コンテナーに入れたいとします。
それとも哺乳類である必要がありますか?おそらく、ある種の異種間搾乳工場が必要なのでしょうか?
それらを一緒にリンクする必要さえありますか?それらが両方とも IPettable であることを知っているだけで十分ですか?
本当に 1 つのクラスだけが必要な場合でも、クラス階層全体を派生させたいという欲求をよく感じます。私はいつかそれが必要になるかもしれないと予想してそれを行いますが、通常は決して行いません。そうした場合でも、通常、それを修正するために多くのことをしなければなりません。それは、私が作成している最初のクラスが犬ではないためです。私はそれほど幸運ではありません。代わりにカモノハシです。現在、私のクラス階層全体は奇妙なケースに基づいており、無駄なコードがたくさんあります。
また、ある時点で、すべての猫が IPettable であるとは限らないことに気付くかもしれません (毛のない猫のように)。これで、そのインターフェイスを適合するすべての派生クラスに移動できます。突然 Cats が PettableBase から派生しなくなったという重大な変更ははるかに少ないことがわかります。
インターフェイスと基本クラスの基本的かつ単純な定義は次のとおりです。
乾杯
これは、このJavaWorldの記事で詳しく説明されています。
個人的には、インターフェイスを使用してインターフェイスを定義する傾向があります。つまり、何かにアクセスする方法を指定するシステム設計の一部です。
1つ以上のインターフェースを実装するクラスがあることは珍しいことではありません。
私が何か他のものの基礎として使用する抽象クラス。
以下は、上記の記事JavaWorld.comの記事、著者Tony Sintes、04/20/01からの抜粋です。
インターフェイスと抽象クラス
インターフェイスと抽象クラスの選択は、どちらかまたは両方の命題ではありません。デザインを変更する必要がある場合は、それをインターフェースにします。ただし、デフォルトの動作を提供する抽象クラスがある場合があります。抽象クラスは、アプリケーションフレームワーク内の優れた候補です。
抽象クラスを使用すると、いくつかの動作を定義できます。彼らはあなたのサブクラスに他の人を提供するように強制します。たとえば、アプリケーションフレームワークがある場合、抽象クラスはイベントやメッセージ処理などのデフォルトサービスを提供する場合があります。これらのサービスにより、アプリケーションをアプリケーションフレームワークにプラグインできます。ただし、アプリケーションでのみ実行できるアプリケーション固有の機能がいくつかあります。このような機能には、多くの場合アプリケーションに依存する起動タスクとシャットダウンタスクが含まれる場合があります。したがって、その動作自体を定義しようとする代わりに、抽象基本クラスは抽象シャットダウンおよび起動メソッドを宣言できます。基本クラスはそれらのメソッドが必要であることを知っていますが、抽象クラスはクラスがそれらのアクションを実行する方法を知らないことを認めさせます。アクションを開始する必要があることだけを知っています。起動するときは、抽象クラスはstartupメソッドを呼び出すことができます。基本クラスがこのメソッドを呼び出すと、Javaは子クラスによって定義されたメソッドを呼び出します。
多くの開発者は、抽象メソッドを定義するクラスがそのメソッドも呼び出すことができることを忘れています。抽象クラスは、計画された継承階層を作成するための優れた方法です。また、クラス階層の非リーフクラスにも適しています。
クラスとインターフェース
インターフェイスの観点からすべてのクラスを定義する必要があると言う人もいますが、推奨は少し極端に思えます。デザインの内容が頻繁に変更されることがわかった場合は、インターフェイスを使用します。
たとえば、ストラテジーパターンを使用すると、新しいアルゴリズムとプロセスを、それらを使用するオブジェクトを変更せずにプログラムにスワップできます。メディアプレーヤーは、CD、MP3、およびwavファイルの再生方法を知っている場合があります。もちろん、これらの再生アルゴリズムをプレーヤーにハードコーディングする必要はありません。そのため、AVIなどの新しいフォーマットを追加するのが難しくなります。さらに、コードには役に立たないcaseステートメントが散らばっています。また、傷害に侮辱を加えるには、新しいアルゴリズムを追加するたびに、これらのケースステートメントを更新する必要があります。全体として、これはプログラムするためのオブジェクト指向の方法ではありません。
ストラテジーパターンを使用すると、オブジェクトの背後にあるアルゴリズムを簡単にカプセル化できます。そうすれば、いつでも新しいメディアプラグインを提供できます。プラグインクラスをMediaStrategyと呼びましょう。そのオブジェクトには、playStream(Stream s)という1つのメソッドがあります。したがって、新しいアルゴリズムを追加するには、アルゴリズムクラスを拡張するだけです。これで、プログラムが新しいメディアタイプに遭遇すると、ストリームの再生をメディア戦略に委任するだけです。もちろん、必要なアルゴリズム戦略を適切にインスタンス化するには、いくつかの配管が必要になります。
これは、インターフェイスを使用するのに最適な場所です。戦略パターンを使用しました。これは、デザイン内で変更される場所を明確に示しています。したがって、戦略をインターフェースとして定義する必要があります。オブジェクトに特定のタイプを持たせたい場合は、通常、継承よりもインターフェイスを優先する必要があります。この場合、MediaStrategyです。タイプIDを継承に依存することは危険です。特定の継承階層にロックされます。Javaでは多重継承が許可されていないため、有用な実装やより多くの型IDを提供するものを拡張することはできません。
可能な限り、継承ではなく構成を使用することをお勧めします。インターフェイスを使用しますが、基本実装にはメンバー オブジェクトを使用します。こうすることで、特定の方法で動作するようにオブジェクトを構築するファクトリを定義できます。動作を変更したい場合は、さまざまなタイプのサブオブジェクトを作成する新しいファクトリ メソッド (または抽象ファクトリ) を作成します。
場合によっては、すべての変更可能な動作がヘルパー オブジェクトで定義されている場合、プライマリ オブジェクトにインターフェイスがまったく必要ないことがあります。
したがって、IPet または PetBase の代わりに、IFurBehavior パラメーターを持つ Pet になる可能性があります。IFurBehavior パラメーターは、PetFactory の CreateDog() メソッドによって設定されます。shed() メソッドで呼び出されるのはこのパラメーターです。
これを行うと、コードがはるかに柔軟になり、単純なオブジェクトのほとんどが非常に基本的なシステム全体の動作を処理することがわかります。
多重継承言語でもこのパターンをお勧めします。
また、オブジェクト指向 (ブログを参照)に流されないように注意し、常に必要な動作に基づいてオブジェクトをモデル化してください。必要な唯一の動作が動物の総称と種だけであるアプリを設計している場合は、必要なのは世界中の可能なすべての動物の数百万のクラスではなく、名前のプロパティを持つ 1 つのクラスの動物。
大まかな経験則があります
機能:すべての部分で異なる可能性があります:インターフェイス。
データと機能、パーツはほとんど同じで、パーツは異なります:抽象クラス。
わずかな変更を加えて拡張した場合に実際に機能するデータと機能:通常の(具体的な)クラス
データと機能、変更は計画されていません:最終修飾子を持つ通常の(具体的な)クラス。
データ、そして多分機能:読み取り専用:列挙型メンバー。
これは非常に大まかな準備ができており、厳密には定義されていませんが、すべてが読み取り専用ファイルのように少し修正される列挙型まで、すべてが変更されることを目的としたインターフェイスからのスペクトルがあります。
ソース: http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C# は、過去 14 年間で成熟し、進化した素晴らしい言語です。これは私たち開発者にとって素晴らしいことです。なぜなら、成熟した言語は自由に使える言語機能を大量に提供してくれるからです。
しかし、多くの力は多くの責任を伴います。これらの機能の一部は誤用される可能性があり、ある機能を別の機能よりも優先して使用する理由を理解するのが難しい場合があります。長年にわたり、多くの開発者が苦労しているのを見てきた機能の 1 つは、インターフェイスの使用を選択するか、抽象クラスの使用を選択するかということです。どちらにも長所と短所があり、それぞれを使用する適切な時間と場所があります。しかし、どうやって決めるのですか?
どちらも、タイプ間で共通の機能を再利用できます。最も明白な違いは、インターフェイスがその機能の実装を提供しないのに対し、抽象クラスでは「基本」または「デフォルト」の動作を実装し、必要に応じてクラスの派生型でこのデフォルトの動作を「オーバーライド」できることです。 .
これはまったく問題なく、コードの再利用性を高め、ソフトウェア開発の DRY (Don't 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 抽象クラスを実装しているという理由だけで、「デフォルト」(ベース クラス) の Bark を無料で継承できます。
しかし、あなたはすでにそれを知っていたと確信しています。
ここにいるのは、抽象クラスではなくインターフェイスを選択する理由、またはその逆を選択する理由を理解したいからです。抽象クラスではなくインターフェイスを選択する理由の 1 つは、デフォルトの実装がない場合、またはデフォルトの実装を回避したい場合です。これは通常、インターフェイスを実装している型が「is a」関係で関連付けられていないためです。実際には、それぞれのタイプが「できる」、または何かをする、または何かを持つ「能力」を持っているという事実を除いて、それらはまったく関連している必要はありません。
一体、それはどういう意味ですか?たとえば、人間はアヒルではありません…そしてアヒルは人間ではありません。かなり明白。ただし、アヒルと人間の両方に泳ぐ「能力」があります (人間が 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 メソッドの動作を処理できるようにします。
概要:
したがって、私の主な経験則は、クラス階層の「デフォルト」機能を実装したい場合、または/および使用しているクラスまたはタイプが「is a」関係を共有する場合に抽象クラスを使用することです (例: poodle「は」 犬の種類)。
一方、「である」関係はなくても、何かを行う、または何かを持つ「能力」を共有するタイプがある場合は、インターフェースを使用します (例: アヒルは人間ではありません。ただし、アヒルと人間は共有します)。泳ぐ「能力」)。
抽象クラスとインターフェースのもう 1 つの違いは、クラスは 1 対多のインターフェースを実装できますが、クラスは 1 つの抽象クラス (または任意のクラス) からしか継承できないことです。はい、クラスをネストして継承階層を持つことはできますが (多くのプログラムはこれを行う必要があります)、1 つの派生クラス定義で 2 つのクラスを継承することはできません (この規則は C# に適用されます。他の一部の言語では、通常、これを行うことができます。これらの言語にはインターフェイスがないためです)。
また、インターフェイスを使用してインターフェイス分離の原則 (ISP) に準拠することも忘れないでください。ISP は、クライアントが使用しない方法に依存することを強制されるべきではないと述べています。このため、インターフェイスは特定のタスクに集中する必要があり、通常は非常に小さいです (例: IDisposable、IComparable )。
もう 1 つのヒントは、小さくて簡潔な機能を開発する場合は、インターフェイスを使用することです。大規模な機能ユニットを設計している場合は、抽象クラスを使用してください。
これが一部の人々にとって問題を解決することを願っています!
また、より良い例を考えたり、何か指摘したいことがあれば、下のコメント欄に記入してください!
インターフェイスは小さくする必要があります。本当に小さい。オブジェクトを本当に分解している場合、インターフェイスにはおそらく非常に具体的なメソッドとプロパティがいくつか含まれているだけです。
抽象クラスはショートカットです。PetBase のすべての派生物が共有していて、一度コーディングすれば完了できるものはありますか? はいの場合は、抽象クラスの時間です。
抽象クラスも制限しています。これらは子オブジェクトを作成するための優れたショートカットを提供しますが、特定のオブジェクトは 1 つの抽象クラスしか実装できません。多くの場合、これは Abstract クラスの制限であり、これが多くのインターフェイスを使用する理由です。
抽象クラスには、複数のインターフェースが含まれる場合があります。PetBase 抽象クラスは、IPet (ペットには所有者がいます) と IDigestion (ペットは食べる、または少なくとも食べる必要があります) を実装できます。ただし、すべてのペットが哺乳類であるとは限らず、すべての哺乳類がペットであるとは限らないため、PetBase はおそらく IMammal を実装しません。PetBase を拡張し、IMammal を追加する MammalPetBase を追加できます。FishBase は PetBase を持ち、IFish を追加できます。IFish は、インターフェイスとして ISwim と IUnderwaterBreather を持ちます。
はい、私の例は単純な例に対して非常に複雑すぎますが、それはインターフェイスと抽象クラスがどのように連携するかについての優れた点の一部です。
インターフェイス上の基本クラスのケースは、サブメイン .NET コーディング ガイドラインで詳しく説明されています。
基本クラスとインターフェイス インターフェイス タイプは値の部分的な説明であり、多くのオブジェクト タイプでサポートされる可能性があります。可能な限り、インターフェイスの代わりに基本クラスを使用してください。バージョン管理の観点からは、クラスはインターフェースよりも柔軟です。クラスを使用すると、バージョン 1.0 を出荷してから、バージョン 2.0 で新しいメソッドをクラスに追加できます。メソッドが抽象でない限り、既存の派生クラスは変更されずに機能し続けます。
インターフェイスは実装の継承をサポートしていないため、クラスに適用されるパターンはインターフェイスには適用されません。インターフェイスにメソッドを追加することは、基本クラスに抽象メソッドを追加することと同じです。クラスが新しいメソッドを実装しないため、インターフェイスを実装するクラスは壊れます。インターフェイスは、次の状況に適しています。
- いくつかの無関係なクラスがプロトコルをサポートしたいと考えています。
- これらのクラスには、既に基本クラスが確立されています (たとえば、ユーザー インターフェイス (UI) コントロールや XML Web サービスなど)。
- 集計は適切ではないか、実行不可能です。他のすべての状況では、クラス継承の方が優れたモデルです。
重要な違いの 1 つは、継承できる基本クラスは 1 つだけですが、多くのインターフェイスを実装できることです。したがって、別の基本クラスも継承する必要がないことが絶対に確実な場合にのみ、基本クラスを使用する必要があります。さらに、インターフェースが大きくなっていることがわかった場合は、独立した機能を定義するいくつかの論理的な部分に分割することを検討し始める必要があります。それらをすべて継承してグループ化するインターフェイス)。
抽象クラスよりもインターフェイスを優先する
理論的根拠、考慮すべき主なポイント [ここですでに言及されている 2 つ] は次のとおりです。
[1] もちろん、より多くのコードが追加されますが、簡潔さが主な関心事である場合は、おそらく最初から Java を避けるべきでした!
[2] Joshua Bloch、Effective Java、アイテム 16 ~ 18。
[3] http://www.codeproject.com/KB/ar ...
私が最初にオブジェクト指向プログラミングについて学び始めたとき、継承を使用して共通の動作を共有するという簡単でおそらくよくある間違いを犯しました。
この特定の質問でよく使用される例をさらに構築するために、ガールフレンド、車、毛羽立った毛布など、かわいがることができるものがたくさんあるので、この共通の動作を提供する Petable クラスと、継承するさまざまなクラスがあったかもしれませんそれから。
ただし、ペットになれることは、これらのオブジェクトの性質の一部ではありません。彼らの本質に不可欠な、はるかに重要な概念があります-ガールフレンドは人であり、車は陸上車両であり、猫は哺乳動物です...
ビヘイビアは最初にインターフェイス (クラスのデフォルト インターフェイスを含む) に割り当て、(a) より大きなクラスのサブセットであるクラスの大規模なグループに共通する場合にのみ、基本クラスに昇格する必要があります。 "cat" と "person" は "mammal" のサブセットです。
問題は、オブジェクト指向設計を最初よりも十分に理解すると、通常は何も考えずに自動的にこれを行うようになることです。したがって、「抽象クラスではなくインターフェースへのコード」というステートメントのありのままの真実は、誰かがわざわざそれを言って、他の意味を読み込もうとするだろうとは信じられないほど明白になります。
私が追加したいもう 1 つのことは、クラスが純粋に抽象的である場合 (子、親、またはクライアントに公開されている非抽象、非継承のメンバーまたはメソッドがない場合) では、なぜクラスなのかということです。場合によってはインターフェイスに、他の場合には Null に置き換えることができます。
概念的には、インターフェイスは、オブジェクトが提供する一連のメソッドを形式的および半形式的に定義するために使用されます。形式的にはメソッド名とシグネチャのセットを意味し、半形式的にはそれらのメソッドに関連付けられた人間が読めるドキュメントを意味します。
インターフェイスは API の説明に過ぎず ( APIはアプリケーション プログラミングインターフェイスの略です)、実装を含めることはできず、インターフェイスを使用または実行することはできません。それらは、オブジェクトと対話する方法の契約を明示するだけです。
クラスは実装を提供し、ゼロ、1 つ、または複数のインターフェイスを実装することを宣言できます。クラスが継承されることを意図している場合、規則では、クラス名の前に「Base」を付けます。
基本クラスと抽象基本クラス(ABC)には違いがあります。ABC では、インターフェイスと実装が混在しています。コンピュータプログラミングの外側の抽象とは、「要約」、つまり「抽象==インターフェース」を意味します。次に、抽象基本クラスは、インターフェイスと、継承されることを意図した空の実装、部分実装または完全実装の両方を記述できます。
インターフェイス、抽象基本クラス、クラスのみをいつ使用するかについての意見は、開発しているものと、開発している言語の両方に基づいて大きく異なります。インターフェイスは、多くの場合、Java や C# などの静的に型付けされた言語にのみ関連付けられますが、動的型付け言語は、インターフェイスと抽象基本クラスを持つこともできます。たとえば Python では、 interfaceを実装することを宣言する Class と、 classのインスタンスであり、そのinterfaceを提供すると言われているオブジェクトとの区別が明確になっています。. 動的言語では、同じクラスのインスタンスである 2 つのオブジェクトが、まったく異なるインターフェイスを提供することを宣言できます。Python では、これはオブジェクト属性に対してのみ可能ですが、メソッドはクラスのすべてのオブジェクト間で状態を共有します。ただし、Ruby では、オブジェクトはインスタンスごとのメソッドを持つことができるため、同じクラスの 2 つのオブジェクト間のインターフェイスは、プログラマーが望むだけ異なる可能性があります (ただし、Ruby にはインターフェイスを宣言する明示的な方法はありません)。
動的言語では、オブジェクトをイントロスペクトし、それが提供するメソッドを尋ねるか ( look before you jump )、できれば単にオブジェクトで目的のインターフェイスを使用しようとし、オブジェクトがそのインターフェースを提供しません(許可よりも許しを求める方が簡単です)。これにより、2 つのインターフェースが同じメソッド名を持っていても意味が異なるという「誤検知」が発生する可能性があります。ただし、トレードオフは、コードのすべての可能な用途を予測するために事前に過度に指定する必要がないため、コードがより柔軟になることです。
心に留めておくべきもう 1 つのオプションは、"has-a" 関係、つまり "is implementation in" または "composition" を使用することです。これは、「is-a」継承を使用するよりもクリーンで柔軟な構造化方法である場合があります。
Dog と Cat の両方が Pet を "持っている" と言うのは、論理的にはあまり意味がないかもしれませんが、一般的な多重継承の落とし穴を回避できます。
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 クラスに高度に結合する必要があります。これは、支払うコストが高くなります。
継承、構成、およびインターフェイスの間に常に正しい方法はありません。3 つのオプションすべてをどのように調和させて使用できるかを検討することが役立ちます。3 つのうち、継承は通常、使用頻度が最も低いオプションです。
それが何を意味し、この場合に適用されることを理解していない限り、基本クラスを使用しないでください。該当する場合はそれを使用し、そうでない場合はインターフェイスを使用します。ただし、小さなインターフェースに関する答えに注意してください。
Public Inheritance は OOD で過剰に使用されており、ほとんどの開発者が認識している、または対応しようとしているよりも多くのことを表現しています。リスコフの代用可能性原理を参照してください
要するに、A が B である場合、A は公開するすべてのメソッドに対して、B 以上を必要とせず、B 以上を配信します。
一般的な実装に抽象クラスを使用することに関する以前のコメントは、間違いなく的を射ています。まだ触れていない利点の 1 つは、インターフェースを使用すると、単体テスト用のモック オブジェクトの実装がはるかに簡単になることです。Jason Cohen が説明したように IPet と PetBase を定義すると、物理データベースのオーバーヘッドなしで、さまざまなデータ条件を簡単に模倣できます (実際にテストする時が来ると判断するまでは)。
C# に関しては、ある意味で、インターフェイスと抽象クラスは交換可能です。ただし、違いは次のとおりです。i) インターフェイスはコードを実装できません。ii) このため、インターフェイスはスタックのさらに上のサブクラスを呼び出すことができません。iii) 1 つのクラスで継承できるのは抽象クラスだけですが、1 つのクラスで複数のインターフェイスを実装できます。
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();
}
さて、すべての哺乳類には毛と乳首があるので (私の知る限り、私は動物学者ではありません)、これを抽象基本クラスにまとめることができます。
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)
}
この設計は、多くの具体的なクラスがあり、インターフェイスにプログラムするためだけにボイラープレートを維持したくない場合に便利です。新しいメソッドがインターフェイスに追加された場合、結果として生じるすべてのクラスが壊れてしまうため、インターフェイス アプローチの利点は引き続き得られます。
この場合、抽象は具体的である可能性もあります。ただし、抽象的な指定は、このパターンが採用されていることを強調するのに役立ちます。
それはあなたの要件に依存します。IPet が十分に単純であれば、それを実装したいと思います。それ以外の場合、PetBase が複製したくない大量の機能を実装している場合は、それを使用してください。
基本クラスを実装することの欠点は、既存のメソッドoverride
(または) に対する要件です。new
これにより、それらは仮想メソッドになります。つまり、オブジェクト インスタンスの使用方法に注意する必要があります。
最後に、.NET の単一の継承は私を殺します。単純な例: ユーザー コントロールを作成しているので、継承するとしますUserControl
。しかし、今では継承もロックアウトされていますPetBase
。PetBase
これにより、代わりにクラスメンバーを作成するなど、再編成が必要になります。
私は通常、必要になるまでどちらも実装しません。私は抽象クラスよりもインターフェースを好みます。継承クラスの一部に共通の動作がある場合は、それを上に移動して抽象基本クラスを作成します。どちらも本質的に同じ目的を果たしているため、両方の必要性はわかりません。両方を持つことは、ソリューションが過剰に設計されているというコードの悪臭 (imho) です。
いつインターフェイスを使用し、いつ基本クラスを使用する必要がありますか?
次の場合はインターフェイスを使用する必要があります
abstract
メソッドがあり、非抽象メソッドがないnon abstract
(インターフェイス メソッドがデフォルトの実装を提供する Java 8 言語を除く)interface
に比べて使いやすくなります。abstract
詳細については、この SEの質問をご覧ください。
メソッドの基本実装を実際に定義したくない場合は、常にインターフェイスにする必要がありますか?
はい。それはより良く、よりきれいです。いくつかの抽象メソッドを持つ基本クラスがある場合でも、基本クラスはabstract
インターフェースを介してメソッドを拡張しましょう。基本クラスを変更せずに、将来インターフェースを変更できます。
Java での例:
abstract class PetBase implements IPet {
// Add all abstract methods in IPet interface and keep base class clean.
Base class will contain only non abstract methods and static methods.
}
犬と猫のクラスがある場合。PetBase の代わりに IPet を実装する必要があるのはなぜですか? ISheds または IBarks (IMakesNoise?) のインターフェイスを持つことは理解できます。これらはペットごとに配置できるためですが、一般的な Pet にどちらを使用すればよいかわかりません。
基本クラスにインターフェイスを実装することを好みます。
abstract class PetBase implements IPet {
// Add all abstract methods in IPet
}
/*If ISheds,IBarks is common for Pets, your PetBase can implement ISheds,IBarks.
Respective implementations of PetBase can change the behaviour in their concrete classes*/
abstract class PetBase implements IPet,ISheds,IBarks {
// Add all abstract methods in respective interfaces
}
利点:
既存のインターフェースに抽象メソッドをもう 1 つ追加したい場合は、抽象基本クラスに触れずに単純にインターフェースを変更します。コントラクトを変更したい場合は、基本クラスに触れずにインターフェイスと実装クラスを変更します。
インターフェイスを介して基本クラスに不変性を提供できます。この記事を見てください
詳細については、関連する SE の質問を参照してください。
インターフェースを使用して、無関係なクラスの ACROSS ファミリーのコントラクトを強制します。たとえば、コレクションを表すクラスに共通のアクセス メソッドがあっても、まったく異なるデータが含まれている場合があります。つまり、1 つのクラスがクエリからの結果セットを表し、もう 1 つのクラスがギャラリー内の画像を表す場合があります。また、複数のインターフェイスを実装できるため、クラスの機能をブレンド (および明示) できます。
クラスが共通の関係を持っているため、類似の構造的および動作的特徴がある場合、継承を使用します。つまり、自動車、オートバイ、トラック、および SUV は、多数の車輪、最高速度を含む可能性があるすべてのタイプの道路車両です。
オブジェクトが持つ必要があるもの、持つもの、または行うものと、オブジェクトが持つことができる(または可能性がある) もの、持つ、または行うもののリストを作成します。Mustは基本型を示し、 canはインターフェイスを示します。
たとえば、PetBaseは呼吸する必要があり、IPet は DoTricks である可能性があります。
問題のドメインを分析すると、正確な階層構造を定義するのに役立ちます。
基本クラスの継承者には、「is a」関係が必要です。インターフェイスは、「実装する」関係を表します。したがって、継承者が関係を維持する場合にのみ、基本クラスを使用してください。
あなた自身の判断を使用し、賢くしてください。他の人 (私のような) が言っていることを常に実行しないでください。「抽象クラスよりもインターフェイスを好む」と聞くかもしれませんが、実際には状況によって異なります。それはクラスが何であるかによって異なります。
オブジェクトの階層がある上記のケースでは、インターフェースは良い考えです。インターフェイスは、これらのオブジェクトのコレクションを操作するのに役立ち、階層の任意のオブジェクトを操作するサービスを実装するときにも役立ちます。階層からオブジェクトを操作するためのコントラクトを定義するだけです。
一方、共通の機能を共有する一連のサービスを実装する場合、共通の機能を完全に別のクラスに分離するか、共通の基本クラスに移動して抽象化し、誰も基本をインスタンス化できないようにすることができます。クラス。
また、時間の経過とともに抽象化をサポートする方法についても検討してください。インターフェイスは固定されています: 任意の型が実装できる一連の機能のコントラクトとしてインターフェイスをリリースします。基本クラスは、時間の経過とともに拡張できます。これらの拡張機能は、すべての派生クラスの一部になります。
Jon Limjapの回答に感謝しますが、インターフェイスと抽象基本クラスの概念について説明を追加したいと思います
インターフェイス型と抽象基本クラス
Pro C# 5.0 および .NET 4.5 Frameworkブックからの適応。
インターフェイスの型は、抽象基本クラスに非常に似ているように見えるかもしれません。クラスが抽象としてマークされている場合、クラスは任意の数の抽象メンバーを定義して、すべての派生型にポリモーフィック インターフェイスを提供できることを思い出してください。ただし、クラスが一連の抽象メンバーを定義する場合でも、任意の数のコンストラクター、フィールド データ、非抽象メンバー (実装あり) などを自由に定義できます。一方、インターフェイスには、抽象メンバー定義のみが含まれます。抽象親クラスによって確立されたポリモーフィック インターフェイスには、派生型のみが抽象親によって定義されたメンバーをサポートするという 1 つの主要な制限があります。ただし、大規模なソフトウェア システムでは、System.Object 以外に共通の親を持たない複数のクラス階層を開発することは非常に一般的です。抽象基本クラスの抽象メンバーが派生型にのみ適用されることを考えると、同じポリモーフィック インターフェイスをサポートするために異なる階層の型を構成する方法はありません。例として、次の抽象クラスを定義したとします。
public abstract class CloneableType
{
// Only derived types can support this
// "polymorphic interface." Classes in other
// hierarchies have no access to this abstract
// member.
public abstract object Clone();
}
この定義により、CloneableType を拡張するメンバーのみが Clone() メソッドをサポートできます。この基本クラスを拡張しない一連の新しいクラスを作成すると、このポリモーフィック インターフェイスを取得できなくなります。また、C# はクラスの多重継承をサポートしていないことを思い出してください。したがって、Car であり、CloneableType である MiniVan を作成したい場合、それを行うことはできません。
// Nope! Multiple inheritance is not possible in C#
// for classes.
public class MiniVan : Car, CloneableType
{
}
ご想像のとおり、インターフェイスの種類が役に立ちます。インターフェイスが定義された後は、任意のクラスまたは構造、任意の階層内、任意の名前空間または任意のアセンブリ (任意の .NET プログラミング言語で記述) 内で実装できます。ご覧のとおり、インターフェイスは非常に多態的です。System 名前空間で定義されている、ICloneable という名前の標準 .NET インターフェイスについて考えてみましょう。このインターフェイスは、Clone() という名前の単一のメソッドを定義します。
public interface ICloneable
{
object Clone();
}
IPet/PetBase の実装に言及しているコメントに加えて、アクセサー ヘルパー クラスを提供することが非常に重要な場合もあります。
IPet/PetBase スタイルは、複数の実装があることを前提としているため、実装が簡素化されるため、PetBase の価値が高まります。ただし、複数のクライアントがあり、この 2 つが逆または混合している場合は、インターフェイスの使用を支援するクラス ヘルプを提供すると、インターフェイスの使用が容易になり、コストを削減できます。
他の開発者が型のメンバーに加えて独自の基本クラスを使用することを望む理由がまったくなく、バージョン管理の問題が予測される場合は、基本クラスを使用する必要があります ( http://haacked.com/archive/2008/02を参照)。 /21/versioning-issues-with-abstract-base-classes-and-interfaces.aspx )。
継承する開発者が独自の基本クラスを使用して型のインターフェイスを実装する何らかの理由があり、インターフェイスの変更が見られない場合は、インターフェイスを使用してください。この場合、便宜上、インターフェースを実装するデフォルトの基本クラスをスローすることができます。
インターフェースには、クラスに対して「ホットスワップ可能」であるという明確な利点があります。クラスをある親から別の親に変更すると、多くの場合、多大な作業が必要になりますが、多くの場合、実装クラスに大きな影響を与えることなく、インターフェイスを削除および変更できます。これは、クラスに実装する「可能性がある」動作の狭いセットがいくつかある場合に特に役立ちます。
これは、私の分野であるゲーム プログラミングで特にうまく機能します。基本クラスは、継承されたオブジェクトが必要とする「可能性がある」大量の動作で肥大化する可能性があります。インターフェイスを使用すると、さまざまな動作をオブジェクトに簡単かつ容易に追加または削除できます。たとえば、ダメージを反射させたいオブジェクトの「IDamageEffects」のインターフェースを作成すれば、それをさまざまなゲーム オブジェクトに簡単に適用でき、後で簡単に変更できます。「静的な」装飾オブジェクトに使用したい初期クラスを設計し、最初はそれらが破壊不可能であると判断したとします。後で、爆破できたらもっと楽しいだろうと判断するかもしれないので、クラスを変更して「IDamageEffects」インターフェイスを実装します。これは、基本クラスを切り替えたり、新しいオブジェクト階層を作成したりするよりもはるかに簡単です。
継承には他にも利点があります。たとえば、変数が親クラスまたは継承されたクラスのいずれかのオブジェクトを保持できるなどです (「オブジェクト」などの一般的なものとして宣言する必要はありません)。
たとえば、.NET WinForms では、ほとんどの UI コンポーネントが System.Windows.Forms.Control から派生しているため、それとして宣言された変数は、ほぼすべての UI 要素 (ボタン、ListView など) を "保持" できます。確かに、アイテムのすべてのプロパティやメソッドにアクセスできるわけではありませんが、基本的なものはすべて揃っているので便利です。