出典: 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つのヒントは、機能の小さな簡潔なビットを開発する場合は、インターフェースを使用することです。大規模な機能ユニットを設計する場合は、抽象クラスを使用します。
これで一部の人の問題が解決することを願っています!
また、より良い例を思いついたり、何か指摘したい場合は、下のコメントでそのようにしてください!