web-dev-qa-db-ja.com

抽象クラスの代わりにインターフェースを使用する場合、およびその逆の場合

これは一般的なOOPの質問かもしれません。使用法に基づいて、インターフェースと抽象クラスの間で一般的な比較をしたいと思いました。

いつインタフェースを使いたいのか、そしていつ抽象クラスを使いたいのか

392
Chirantan

私はそれについての記事を書きました:

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

要約すると:

抽象クラスについて話すとき、私たちはオブジェクト型の特性を定義しています。 オブジェクトが何であるかを指定する

インターフェイスについて話し、提供することを約束する機能を定義するときには、オブジェクトができることについてについて契約を結ぶことについて話しています。

388
Jorge Córdoba

抽象クラスは、共有状態または機能を持つことができます。インターフェースは、状態や機能を提供するための約束にすぎません。優れた抽象クラスは、機能や状態を共有できるため、書き直す必要があるコードの量を減らすことができます。インターフェースには、共有する定義済み情報がありません

401
Alex

個人的には、抽象クラスを書く必要はほとんどありません。

ほとんどの場合、抽象クラスが(誤って)使用されているように見えますが、これは抽象クラスの作成者が "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つの概念を過度に結び付けているということです。

  1. 2つの項目を比較する方法(どの項目が最初になるべきか)
  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"関数を "作成"することだけです。少ないコードで.

79

これは素人の言葉です(間違っている場合は遠慮なく訂正してください) - このトピックは不気味ですが、いつか他の人がつまずくことがあります...

抽象クラスを使用すると、青写真を作成したり、その子孫すべてに持たせたいプロパティやメソッドをさらにCONSTRUCT(実装)したりすることができます。

一方、インターフェースでは、特定の名前を持つプロパティやメソッドをそれを実装するすべてのクラスに存在させる必要があると宣言することしかできません。ただし、実装方法を指定することはできません。また、クラスは多くのインタフェースを実装できますが、抽象クラスは1つしか拡張できません。インタフェースは、より高水準のアーキテクチャツール(デザインパターンを把握し始めるとより明確になります)です。要約は両方のキャンプに足があり、いくつかの汚れた作業も実行できます。

なぜ一方をもう一方に使用するのですか?前者は、より多くのconcreteの子孫の定義を許可します - 後者は、より大きいpolymorphismを許可します。この最後の点は、エンドユーザ/コーダにとって重要です。エンドユーザ/コーダは、さまざまな組み合わせ/形状でAPI(nterface)を実装するためにこの情報を利用できます。彼らのニーズに合うように。

私にとってこれは「電球」の瞬間だと思います - インターフェイスを作者の視点から見て少なくし、プロジェクトの実装を追加しているチェーンの後半に来るコーダーのインターフェイスからもっと考えてみてください、またはAPIの拡張.

39
sunwukung

私の2セント:

インターフェースは基本的にコントラクトを定義します。どの実装クラスも固執しなければならないということです(インターフェースメンバーを実装する)。コードは含まれていません。

一方、抽象クラスはコードを含むことができ、継承クラスが実装しなければならない抽象としてマークされたメソッドがあるかもしれません。

私が抽象クラスを使用した稀な状況は、私が継承クラスがオーバーライドするのに面白くないかもしれないデフォルト機能を持っているときです。

例(非常に初歩的なもの!):CalculatePayment()CalculateRewardPoints()のような抽象メソッドとGetName()SavePaymentDetails()のような非抽象メソッドを持つCustomerという基本クラスを考えてみましょう。

RegularCustomerGoldCustomerのような特殊化されたクラスは、Customer基本クラスから継承し、それら自身のCalculatePayment()およびCalculateRewardPoints()メソッドロジックを実装しますが、GetName()およびSavePaymentDetails()メソッドを再利用します。

古いバージョンを使用していた子クラスに影響を与えずに、抽象クラス(つまり非抽象メソッド)にさらに機能を追加できます。インタフェースにメソッドを追加すると、新しく追加されたインタフェースメンバを実装する必要があるため、それを実装しているすべてのクラスに影響します。

すべての抽象メンバーを持つ抽象クラスは、インターフェースに似ています。

37
PreethaA

JavaをOOPの言語として見ているのであれば、

インタフェースでメソッド実装が提供されない」は、Java 8の起動では無効になりました。現在、Javaはデフォルトメソッドのインターフェースに実装を提供します。

簡単に言えば、私は使いたい

interface:複数の関係のないオブジェクトによるコントラクトを実装するため。これは "HAS A"機能を提供します。

抽象クラス:複数の関連オブジェクト間で同じまたは異なる動作を実装すること。それは "IS A"関係を確立します。

Oracle Webサイト には、interfaceクラスとabstractクラスの主な違いが記載されています。

次の場合は抽象クラスの使用を検討してください。

  1. あなたはいくつかの密接に関連したクラスの間でコードを共有したいです。
  2. あなたはあなたの抽象クラスを拡張するクラスが多くの一般的なメソッドやフィールドを持っている、あるいはパブリック以外のアクセス修飾子を必要とすることを期待しています(プロテクトやプライベートなど)。
  3. あなたは、非静的または非最終フィールドを宣言したいのです。

以下の場合にはインターフェースの使用を検討してください。

  1. 無関係なクラスがあなたのインターフェースを実装することを期待しています。たとえば、多くの無関係なオブジェクトがSerializableインターフェースを実装できます。
  2. 特定のデータ型の動作を指定したいが、だれがその動作を実装しているかを気にする必要はありません。
  3. あなたは型の多重継承を利用したいのです。

例:

抽象クラス(IS A relation)

Reader は抽象クラスです。

BufferedReaderReaderです。

FileReaderReaderです

FileReaderBufferedReaderは、共通の目的で使用されます。データの読み取り、およびそれらはReaderクラスによって関連付けられます。

インタフェース(HAS A capability)

Serializable はインターフェースです。

アプリケーションに2つのクラスがあり、それらがSerializableインターフェースを実装しているとします。

Employee implements Serializable

Game implements Serializable

ここでは、SerializableEmployeeの間のGameインターフェースを介して関係を確立することはできません。これらは異なる目的のためのものです。どちらも状態を直列化することができ、比較はそこで終わります。

これらの記事を見てください。

InterfaceクラスとAbstractクラスの違いをどのように説明すればよいですか?

36
Ravindra babu

あなたの心に明確な概念があるならば、何をするべきかは非常に単純なことです。

抽象クラスは派生できますが、インタフェースは実装できます。両者には違いがあります。 Abstractクラスを派生させると、派生クラスと基本クラスの間の関係は 'is'の関係になります。たとえば、犬は動物、羊は動物です。つまり、派生クラスは基本クラスからいくつかのプロパティを継承しています。

インタフェースの実装に関しては、関係は「あり得ます」です。例えば、犬はスパイ犬であり得る。犬はサーカスの犬になることができます。犬は競走犬になることができます。つまり、何かを取得するために特定のメソッドを実装するということです。

私は私がはっきりしていることを願っています。

29
Aamir

私はいつ抽象クラスを使うべきか、そしていつインターフェースを使うべきかについての記事を書きました。 「1つのIS-A ...と1つのCAN-DO ...」以外には、もっと多くの違いがあります。私にとっては、これらは定評のある答えです。どちらを使用するかについては、いくつかの理由があります。それが役に立てば幸い。

http://codeofdoom.com/wordpress/2009/02/12/learn-this-when-to-use-an-abstract-class-and-an-interface/

10
marcel

1.無関係のクラスに共通の機能を提供するものを作成している場合は、インタフェースを使用します。

2.階層内で密接に関連しているオブジェクト用に何かを作成している場合は、抽象クラスを使用します。

10
kuttychutty

最も簡潔にすると、次のようになります。

共有プロパティ=>抽象クラス。
共有機能=>インターフェース。

そして簡潔に言うと….

抽象クラスの例:

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が正しいことを要求していますが、その実装はまだわかりません。そのような場合、インタフェースの実装を抽象化し、その実装をそのサブクラスに委譲することができます。

最後に、抽象クラスの例ではさまざまなオブジェクトの共有プロパティを操作でき、インターフェイスの例ではさまざまなオブジェクトの共有機能を呼び出すことができたことを覚えていますか。この最後の例では、両方を実行できます。

6
Domn Werner

クラスは1つの基本クラスからしか継承できないため、抽象クラスを使用してクラスのグループに多態性を提供したい場合は、それらすべてがそのクラスから継承する必要があります。抽象クラスは、すでに実装されているメンバを提供することもあります。したがって、抽象クラスではある程度の同一機能を保証できますが、インタフェースでは不可能です。

コンポーネントの多態性を提供するために、インターフェイスと抽象クラスのどちらを使用するかを決めるのに役立つ推奨事項がいくつかあります。

  • コンポーネントのバージョンを複数作成することを予定している場合は、抽象クラスを作成します。抽象クラスは、コンポーネントを簡単にバージョン管理する方法を提供します。基本クラスを更新することによって、すべての継承クラスが変更に合わせて自動的に更新されます。一方、インタフェースは、一度作成したものでは変更できません。新しいバージョンのインターフェースが必要な場合は、まったく新しいインターフェースを作成する必要があります。
  • 作成している機能が、さまざまな種類の異なるオブジェクトにまたがって役立つ場合は、インターフェイスを使用してください。抽象クラスは主に密接に関連しているオブジェクトに使用されるべきですが、インタフェースは関連のないクラスに共通の機能を提供するのに最も適しています。
  • 小さくて簡潔な機能を設計する場合は、インターフェースを使用してください。大規模な機能単位を設計している場合は、抽象クラスを使用してください。
  • コンポーネントのすべての実装に共通の実装済み機能を提供したい場合は、抽象クラスを使用してください。抽象クラスを使用すると、クラスを部分的に実装できますが、インターフェイスにはメンバーの実装はありません。

コピー元:
http://msdn.Microsoft.com/ja-jp/library/scsyfw1d%28v=vs.71%29.aspx

4
Mazhar

これらの文のいずれかが状況に当てはまる場合は、抽象クラスの使用を検討してください。

  1. あなたはいくつかの密接に関連したクラスの間でコードを共有したいです。
  2. あなたはあなたの抽象クラスを拡張するクラスが多くの共通のメソッドやフィールドを持っているか、パブリック以外のアクセス修飾子を必要とすることを期待しています(例えばプロテクトやプライベートなど)。
  3. あなたは、非静的または非最終フィールドを宣言したいのです。これにより、自分が属するオブジェクトの状態にアクセスして変更できるメソッドを定義できます。

これらの文のいずれかが状況に当てはまる場合は、インタフェースの使用を検討してください。

  1. 無関係なクラスがあなたのインターフェースを実装することを期待しています。たとえば、ComparableとCloneableのインタフェースは、無関係な多くのクラスによって実装されています。
  2. 特定のデータ型の動作を指定したいが、だれがその動作を実装しているかを気にする必要はありません。
  3. あなたは多重継承を利用したいのです。

出典

インタフェースよりも抽象クラスを好む場合は?

  1. プログラム/プロジェクトの存続期間を通じて基本クラスを更新することを計画している場合は、基本クラスを抽象クラスにすることをお勧めします。
  2. 階層内で密接に関連しているオブジェクトのバックボーンを構築しようとしている場合は、抽象クラスを使用することは非常に有益です。

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

  1. 大規模な階層型のフレームワークを扱っていないのであれば、インターフェースは素晴らしい選択です。
  2. 多重継承は抽象クラスではサポートされていないため(ダイヤモンド問題)、インターフェースはその日の時間を節約することができます。
3
Satya

私にとっては、私は多くの場合インターフェースを使います。しかし、場合によっては抽象クラスが好きです。

OOのクラスは一般に実装を意味します。実装の詳細を子に強制する場合は抽象クラスを使用し、それ以外の場合はインターフェイスを使用します。

もちろん、抽象クラスは実装を強制するだけでなく、関連する多くのクラスで特定の詳細を共有するのにも役立ちます。

2
La VloZ Merrill

答えは言語によって異なります。たとえば、Javaでは、クラスは複数のインタフェースを実装(継承)できますが、1つの抽象クラスからしか継承できません。そのため、インターフェースはあなたにもっと柔軟性を与えます。しかし、これはC++では正しくありません。

2
Nick Fortescue

いくつかの基本的な実装を提供したい場合は抽象クラスを使用してください。

1
Sebastian

javaでは、1つの(抽象)クラスから継承して機能を「提供する」ことができ、また多くのインターフェースを実装して機能を「保証する」ことができます。

1
Peter Miehle

インタフェースが抽象クラスよりも優れている興味深い場所の1つは、(関連または無関係の)オブジェクトのグループに機能を追加する必要がある場合です。もしあなたがそれらに基本抽象クラスを与えることができないなら(例えばそれらがsealedであるか既に親を持っている)、代わりにそれらにダミー(空の)インタフェースを与えてそれから単にそのインタフェースのための拡張メソッドを書くことができます。

1
Dmitri Nesteruk

純粋に継承に基づいて、明確に子孫を定義するところでは抽象を使用し、抽象的な関係(すなわちanimal-> cat)および/または仮想または非パブリックプロパティの継承、特に共有状態(インタフェースではサポートできない)の継承が必要です。

できる限り継承よりも(依存性注入による)合成を試みるようにしてください。また、契約であるインターフェースは単体テスト、懸念の分離、および(言語によって異なる)多重継承をサポートしないという点に注意してください。

1
annakata

これは非常に難しい電話になることがあります...

1つのポインタを渡すことができます。オブジェクトは多くのインターフェイスを実装できますが、オブジェクトは1つの基本クラスしか継承できません(c#のような現代のOO言語では、C++は多重継承を持っています)。に?)

0
Mesh

抽象クラスは実装を持つことができます。

インターフェースは実装を持っていません、それは単に一種のコントラクトを定義します。

言語依存の違いもあります。たとえば、C#には多重継承がありませんが、1つのクラスに複数のインタフェースを実装できます。

0
Gerrie Schenck