これは一般的なOOPの質問かもしれません。使用法に基づいて、インターフェースと抽象クラスの間で一般的な比較をしたいと思いました。
いつインタフェースを使いたいのか、そしていつ抽象クラスを使いたいのか?
私はそれについての記事を書きました:
要約すると:
抽象クラスについて話すとき、私たちはオブジェクト型の特性を定義しています。 オブジェクトが何であるかを指定する。
インターフェイスについて話し、提供することを約束する機能を定義するときには、オブジェクトができることについてについて契約を結ぶことについて話しています。
抽象クラスは、共有状態または機能を持つことができます。インターフェースは、状態や機能を提供するための約束にすぎません。優れた抽象クラスは、機能や状態を共有できるため、書き直す必要があるコードの量を減らすことができます。インターフェースには、共有する定義済み情報がありません
個人的には、抽象クラスを書く必要はほとんどありません。
ほとんどの場合、抽象クラスが(誤って)使用されているように見えますが、これは抽象クラスの作成者が "Template method"パターンを使用しているためです。
「テンプレートメソッド」の問題は、ほとんど常にリエントラントであるということです。「派生」クラスは、実装している基本クラスの「抽象」メソッドだけでなく、基本クラスのパブリックメソッドについても知っています。ほとんどの場合、それらを呼び出す必要はありませんが。
(過度に単純化された)例:
abstract class QuickSorter
{
public void Sort(object[] items)
{
// implementation code that somewhere along the way calls:
bool less = compare(x,y);
// ... more implementation code
}
abstract bool compare(object lhs, object rhs);
}
そこでここでは、このクラスの作者は一般的なアルゴリズムを書いており、人々が自分自身の「フック」を提供することによってそれを「特殊化」することによってそれを使うことを意図しています。
そのため、意図した使い方は次のようになります。
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
これに関する問題は、あなたが2つの概念を過度に結び付けているということです。
上のコードでは、理論的には、 "compare"メソッドの作者はre-entrantlyスーパークラスの "Sort"メソッドにコールバックできます。決してこれを望んでいないし、必要としないでしょう。
この不要なカップリングに対して支払う代償は、スーパークラスを変更するのが難しいことです。そしてほとんどのOO言語では、実行時にそれを変更することは不可能です。
代わりの方法は、代わりに "Strategy"デザインパターンを使用することです。
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
それで、今注目してください:私たちが持っているのは、インターフェース、およびそれらのインターフェースの具体的な実装だけです。実際には、高レベルのOOデザインを行うために他に必要なものはありません。
"QuickSort"クラスと "NameComparator"を使用して "名前の並べ替え"を実装したという事実を "隠す"ために、ファクトリメソッドをどこかに書くかもしれません。
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
Any基本クラスと派生クラスの間に自然なリエントラント関係がある場合でも、抽象クラスがあるときにこれを実行できます。それらは明示的です。
1つの最後の考え:上で行ったことは、関数型プログラミング言語では、 "QuickSort"関数と "NameComparison"関数を使用して "NameSorting"関数を "作成"することだけです。少ないコードで.
これは素人の言葉です(間違っている場合は遠慮なく訂正してください) - このトピックは不気味ですが、いつか他の人がつまずくことがあります...
抽象クラスを使用すると、青写真を作成したり、その子孫すべてに持たせたいプロパティやメソッドをさらにCONSTRUCT(実装)したりすることができます。
一方、インターフェースでは、特定の名前を持つプロパティやメソッドをそれを実装するすべてのクラスに存在させる必要があると宣言することしかできません。ただし、実装方法を指定することはできません。また、クラスは多くのインタフェースを実装できますが、抽象クラスは1つしか拡張できません。インタフェースは、より高水準のアーキテクチャツール(デザインパターンを把握し始めるとより明確になります)です。要約は両方のキャンプに足があり、いくつかの汚れた作業も実行できます。
なぜ一方をもう一方に使用するのですか?前者は、より多くのconcreteの子孫の定義を許可します - 後者は、より大きいpolymorphismを許可します。この最後の点は、エンドユーザ/コーダにとって重要です。エンドユーザ/コーダは、さまざまな組み合わせ/形状でAPI(nterface)を実装するためにこの情報を利用できます。彼らのニーズに合うように。
私にとってこれは「電球」の瞬間だと思います - インターフェイスを作者の視点から見て少なくし、プロジェクトの実装を追加しているチェーンの後半に来るコーダーのインターフェイスからもっと考えてみてください、またはAPIの拡張.
私の2セント:
インターフェースは基本的にコントラクトを定義します。どの実装クラスも固執しなければならないということです(インターフェースメンバーを実装する)。コードは含まれていません。
一方、抽象クラスはコードを含むことができ、継承クラスが実装しなければならない抽象としてマークされたメソッドがあるかもしれません。
私が抽象クラスを使用した稀な状況は、私が継承クラスがオーバーライドするのに面白くないかもしれないデフォルト機能を持っているときです。
例(非常に初歩的なもの!):CalculatePayment()
、CalculateRewardPoints()
のような抽象メソッドとGetName()
、SavePaymentDetails()
のような非抽象メソッドを持つCustomerという基本クラスを考えてみましょう。
RegularCustomer
やGoldCustomer
のような特殊化されたクラスは、Customer
基本クラスから継承し、それら自身のCalculatePayment()
およびCalculateRewardPoints()
メソッドロジックを実装しますが、GetName()
およびSavePaymentDetails()
メソッドを再利用します。
古いバージョンを使用していた子クラスに影響を与えずに、抽象クラス(つまり非抽象メソッド)にさらに機能を追加できます。インタフェースにメソッドを追加すると、新しく追加されたインタフェースメンバを実装する必要があるため、それを実装しているすべてのクラスに影響します。
すべての抽象メンバーを持つ抽象クラスは、インターフェースに似ています。
JavaをOOPの言語として見ているのであれば、
「インタフェースでメソッド実装が提供されない」は、Java 8の起動では無効になりました。現在、Javaはデフォルトメソッドのインターフェースに実装を提供します。
簡単に言えば、私は使いたい
interface:複数の関係のないオブジェクトによるコントラクトを実装するため。これは "HAS A"機能を提供します。
抽象クラス:複数の関連オブジェクト間で同じまたは異なる動作を実装すること。それは "IS A"関係を確立します。
Oracle Webサイト には、interface
クラスとabstract
クラスの主な違いが記載されています。
次の場合は抽象クラスの使用を検討してください。
以下の場合にはインターフェースの使用を検討してください。
Serializable
インターフェースを実装できます。例:
抽象クラス(IS A relation)
Reader は抽象クラスです。
BufferedReader はReader
です。
FileReader はReader
です
FileReader
とBufferedReader
は、共通の目的で使用されます。データの読み取り、およびそれらはReader
クラスによって関連付けられます。
インタフェース(HAS A capability)
Serializable はインターフェースです。
アプリケーションに2つのクラスがあり、それらがSerializable
インターフェースを実装しているとします。
Employee implements Serializable
Game implements Serializable
ここでは、Serializable
とEmployee
の間のGame
インターフェースを介して関係を確立することはできません。これらは異なる目的のためのものです。どちらも状態を直列化することができ、比較はそこで終わります。
これらの記事を見てください。
あなたの心に明確な概念があるならば、何をするべきかは非常に単純なことです。
抽象クラスは派生できますが、インタフェースは実装できます。両者には違いがあります。 Abstractクラスを派生させると、派生クラスと基本クラスの間の関係は 'is'の関係になります。たとえば、犬は動物、羊は動物です。つまり、派生クラスは基本クラスからいくつかのプロパティを継承しています。
インタフェースの実装に関しては、関係は「あり得ます」です。例えば、犬はスパイ犬であり得る。犬はサーカスの犬になることができます。犬は競走犬になることができます。つまり、何かを取得するために特定のメソッドを実装するということです。
私は私がはっきりしていることを願っています。
私はいつ抽象クラスを使うべきか、そしていつインターフェースを使うべきかについての記事を書きました。 「1つのIS-A ...と1つのCAN-DO ...」以外には、もっと多くの違いがあります。私にとっては、これらは定評のある答えです。どちらを使用するかについては、いくつかの理由があります。それが役に立てば幸い。
1.無関係のクラスに共通の機能を提供するものを作成している場合は、インタフェースを使用します。
2.階層内で密接に関連しているオブジェクト用に何かを作成している場合は、抽象クラスを使用します。
最も簡潔にすると、次のようになります。
共有プロパティ=>抽象クラス。
共有機能=>インターフェース。
そして簡潔に言うと….
抽象クラスの例:
public abstract class BaseAnimal
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
}
public class Dog : BaseAnimal
{
public Dog() : base(4) { }
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
}
動物は共有プロパティ(この場合は足の数)を持っているので、この共有プロパティを含む抽象クラスを作るのは理にかなっています。これにより、そのプロパティを操作する共通のコードを書くこともできます。例えば:
public static int CountAllLegs(List<BaseAnimal> animals)
{
int legCount = 0;
foreach (BaseAnimal animal in animals)
{
legCount += animal.NumberOfLegs;
}
return legCount;
}
インターフェース例:
public interface IMakeSound
{
void MakeSound();
}
public class Car : IMakeSound
{
public void MakeSound() => Console.WriteLine("Vroom!");
}
public class Vuvuzela : IMakeSound
{
public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");
}
ここでVuvuzelasとCarsは完全に異なるものであることに注意してください、しかしそれらは機能を共有しました:音を作ること。したがって、インターフェースはここでは意味があります。さらに、プログラマーは共通のインターフェース(この場合はIMakeSound
)の下でサウンドを構成するものをまとめることができます。この設計では、次のようなコードを書くことができます。
List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());
foreach (IMakeSound soundMaker in soundMakers)
{
soundMaker.MakeSound();
}
何が出力されるのか教えてもらえますか。
最後に、2つを組み合わせることができます。
組み合わせ例:
public interface IMakeSound
{
void MakeSound();
}
public abstract class BaseAnimal : IMakeSound
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
public abstract void MakeSound();
}
public class Cat : BaseAnimal
{
public Cat() : base(4) { }
public override void MakeSound() => Console.WriteLine("Meow!");
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
public override void MakeSound() => Console.WriteLine("Hello, world!");
}
ここでは、すべてのBaseAnimal
が正しいことを要求していますが、その実装はまだわかりません。そのような場合、インタフェースの実装を抽象化し、その実装をそのサブクラスに委譲することができます。
最後に、抽象クラスの例ではさまざまなオブジェクトの共有プロパティを操作でき、インターフェイスの例ではさまざまなオブジェクトの共有機能を呼び出すことができたことを覚えていますか。この最後の例では、両方を実行できます。
クラスは1つの基本クラスからしか継承できないため、抽象クラスを使用してクラスのグループに多態性を提供したい場合は、それらすべてがそのクラスから継承する必要があります。抽象クラスは、すでに実装されているメンバを提供することもあります。したがって、抽象クラスではある程度の同一機能を保証できますが、インタフェースでは不可能です。
コンポーネントの多態性を提供するために、インターフェイスと抽象クラスのどちらを使用するかを決めるのに役立つ推奨事項がいくつかあります。
コピー元:
http://msdn.Microsoft.com/ja-jp/library/scsyfw1d%28v=vs.71%29.aspx
これらの文のいずれかが状況に当てはまる場合は、抽象クラスの使用を検討してください。
これらの文のいずれかが状況に当てはまる場合は、インタフェースの使用を検討してください。
インタフェースよりも抽象クラスを好む場合は?
抽象クラスよりもインターフェースを優先する場合は?
私にとっては、私は多くの場合インターフェースを使います。しかし、場合によっては抽象クラスが好きです。
OOのクラスは一般に実装を意味します。実装の詳細を子に強制する場合は抽象クラスを使用し、それ以外の場合はインターフェイスを使用します。
もちろん、抽象クラスは実装を強制するだけでなく、関連する多くのクラスで特定の詳細を共有するのにも役立ちます。
答えは言語によって異なります。たとえば、Javaでは、クラスは複数のインタフェースを実装(継承)できますが、1つの抽象クラスからしか継承できません。そのため、インターフェースはあなたにもっと柔軟性を与えます。しかし、これはC++では正しくありません。
いくつかの基本的な実装を提供したい場合は抽象クラスを使用してください。
javaでは、1つの(抽象)クラスから継承して機能を「提供する」ことができ、また多くのインターフェースを実装して機能を「保証する」ことができます。
インタフェースが抽象クラスよりも優れている興味深い場所の1つは、(関連または無関係の)オブジェクトのグループに機能を追加する必要がある場合です。もしあなたがそれらに基本抽象クラスを与えることができないなら(例えばそれらがsealed
であるか既に親を持っている)、代わりにそれらにダミー(空の)インタフェースを与えてそれから単にそのインタフェースのための拡張メソッドを書くことができます。
純粋に継承に基づいて、明確に子孫を定義するところでは抽象を使用し、抽象的な関係(すなわちanimal-> cat)および/または仮想または非パブリックプロパティの継承、特に共有状態(インタフェースではサポートできない)の継承が必要です。
できる限り継承よりも(依存性注入による)合成を試みるようにしてください。また、契約であるインターフェースは単体テスト、懸念の分離、および(言語によって異なる)多重継承をサポートしないという点に注意してください。
これは非常に難しい電話になることがあります...
1つのポインタを渡すことができます。オブジェクトは多くのインターフェイスを実装できますが、オブジェクトは1つの基本クラスしか継承できません(c#のような現代のOO言語では、C++は多重継承を持っています)。に?)
抽象クラスは実装を持つことができます。
インターフェースは実装を持っていません、それは単に一種のコントラクトを定義します。
言語依存の違いもあります。たとえば、C#には多重継承がありませんが、1つのクラスに複数のインタフェースを実装できます。