web-dev-qa-db-de.com

Wann ist eine Schnittstelle anstelle einer abstrakten Klasse zu verwenden und umgekehrt?

Dies kann eine generische OOP - Frage sein. Ich wollte einen generischen Vergleich zwischen einer Schnittstelle und einer abstrakten Klasse anhand ihrer Verwendung durchführen. 

Wann würde man eine Schnittstelle verwenden wollen und wann würde man eine abstrakte Klasseverwenden wollen?

374
Chirantan

Ich habe einen Artikel darüber geschrieben:

Abstrakte Klassen und Interfaces

Zusammenfassend:

Wenn wir über abstrakte Klassen sprechen, definieren wir Merkmale eines Objekttyps. Festlegen , was ein Objekt ist .

Wenn wir über eine Schnittstelle sprechen und Fähigkeiten definieren, die wir zugesagt haben, sprechen wir über den Abschluss eines Vertrages über , was das Objekt tun kann.

405
Jorge Córdoba

Eine abstrakte Klasse kann einen gemeinsamen Status oder eine gemeinsame Funktionalität haben. Eine Schnittstelle ist nur ein Versprechen, den Status oder die Funktionalität bereitzustellen. Eine gute abstrakte Klasse reduziert die Menge an Code, die neu geschrieben werden muss, da die Funktionalität oder der Status gemeinsam genutzt werden können. Die Schnittstelle hat keine definierten Informationen, die gemeinsam genutzt werden sollen

394
Alex

Ich persönlich habe fast nie die Notwendigkeit, abstrakte Klassen zu schreiben.

Meistens sehe ich, dass abstrakte Klassen (falsch) verwendet werden, weil der Autor der abstrakten Klasse das Muster "Template-Methode" verwendet.

Das Problem bei der "Template-Methode" ist, dass es fast immer etwas re-entry ist. Die "abgeleitete" Klasse kennt nicht nur die "abstrakte" Methode ihrer Basisklasse, die sie implementiert, sondern auch die öffentlichen Methoden der Basisklasse , obwohl es meistens nicht nötig ist, sie anzurufen.

(Zu stark vereinfachtes) Beispiel:

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);
}

Daher hat der Autor dieser Klasse einen generischen Algorithmus geschrieben und beabsichtigt, ihn durch "Spezialisieren" zu verwenden, indem er seine eigenen "Hooks" bereitstellt - in diesem Fall eine "Compare" -Methode.

Die beabsichtigte Verwendung ist also etwa so:

class NameSorter : QuickSorter
{
    public bool compare(object lhs, object rhs)
    {
        // etc.
    }
}

Das Problem dabei ist, dass Sie zwei Konzepte unangemessen miteinander verbunden haben:

  1. Eine Möglichkeit zum Vergleich zweier Artikel (welcher Artikel sollte zuerst gehen)
  2. Eine Methode zum Sortieren von Elementen (d. H. Quicksort vs Merge Sort usw.)

Im obigen Code kann der Autor der "compare" -Methode theoretisch wieder einleitend in die Superklasse "Sort" -Methode zurückrufen ... selbst wenn sie dies in der Praxis niemals wollen oder brauchen wird .

Der Preis, den Sie für diese nicht benötigte Kopplung zahlen, ist, dass es schwierig ist, die Oberklasse zu ändern, und in den meisten OO - Sprachen ist es unmöglich, sie zur Laufzeit zu ändern.

Die alternative Methode besteht darin, stattdessen das Entwurfsmuster "Strategie" zu verwenden:

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;
    }
}

Also jetzt beachten: Alles, was wir haben, sind Schnittstellen und konkrete Implementierungen dieser Schnittstellen. In der Praxis benötigen Sie nicht wirklich etwas anderes, um ein High-Level-Design OO durchzuführen.

Um die Tatsache zu "verbergen", dass wir die "Sortierung von Namen" mithilfe einer "QuickSort" -Klasse und eines "NameComparator" implementiert haben, schreiben wir möglicherweise noch irgendwo eine Factory-Methode:

ISorter CreateNameSorter()
{
    return new QuickSorter(new NameComparator());
}

Any Wenn Sie über eine abstrakte Klasse verfügen, können Sie dies tun ... Selbst wenn zwischen der Basisklasse und der abgeleiteten Klasse eine natürliche Wiedereintrittsbeziehung besteht, lohnt es sich normalerweise, sie explizit zu machen.

Ein letzter Gedanke: Alles, was wir oben gemacht haben, ist, eine Funktion "NameSorting" mithilfe einer Funktion "QuickSort" und einer Funktion "NameComparison" zu "komponieren". In einer funktionalen Programmiersprache wird diese Art der Programmierung noch natürlicher. mit weniger Code.

75

OK, ich habe das gerade selbst "geschimpft" - hier ist es in Laienbegriffen (zögern Sie nicht, mich zu korrigieren, wenn ich mich irre) - ich weiß, dass dieses Thema oooooold ist, aber eines Tages könnte jemand anderes darüber stolpern ...

Mit abstrakten Klassen können Sie einen Entwurf erstellen und zusätzlich Eigenschaften und Methoden KONSTRUIEREN (implementieren), über die ALLE Nachkommen verfügen sollen.

Auf der anderen Seite können Sie in einer Schnittstelle nur deklarieren, dass Eigenschaften und/oder Methoden mit einem bestimmten Namen in allen Klassen vorhanden sein sollen, die sie implementieren - ohne jedoch anzugeben, wie Sie sie implementieren sollen. Außerdem kann eine Klasse MANY-Schnittstellen implementieren, jedoch nur EINE Abstract-Klasse erweitern. Eine Benutzeroberfläche ist eher ein architektonisches Werkzeug auf hohem Niveau (was klarer wird, wenn Sie anfangen, Entwurfsmuster zu verstehen) - ein Abstract hat in beiden Lagern einen Fuß und kann auch einen Teil der schmutzigen Arbeit verrichten.

Warum übereinander verwenden? Ersteres ermöglicht eine größerekonkreteDefinition von Nachkommen - letzteres ermöglicht eine größerePolymorphie. Dieser letzte Punkt ist für den Endbenutzer/Codierer wichtig, der diese Informationen verwenden kann, um den A.P .I (nterface) in einer Vielzahl von Kombinationen/Formen zu implementieren, um ihren Bedürfnissen zu entsprechen.

Ich denke, dies war der "Glühbirnen" -Moment für mich - denken Sie an Schnittstellen, die weniger aus der Sicht des Autors als vielmehr aus der Sicht eines späteren Programmierers stammen, der einem Projekt die Implementierung hinzufügt, odereine API erweitern.

40
sunwukung

Meine zwei Cent:

Eine Schnittstelle definiert im Wesentlichen einen Vertrag, an den sich jede implementierende Klasse halten muss (die Schnittstellenmitglieder implementieren). Es enthält keinen Code.

Auf der anderen Seite kann eine abstrakte Klasse Code enthalten, und es gibt möglicherweise einige als abstrakt markierte Methoden, die eine erbende Klasse implementieren muss.

In den seltenen Situationen, in denen ich abstrakte Klassen verwendet habe, gibt es einige Standardfunktionalitäten, bei denen die erbende Klasse möglicherweise nicht so interessant ist, beispielsweise eine abstrakte Basisklasse, von der einige spezialisierte Klassen erben.

Beispiel (sehr rudimentär!): Betrachten Sie eine Basisklasse namens Customer, die abstrakte Methoden wie CalculatePayment(), CalculateRewardPoints() und einige nicht-abstrakte Methoden wie GetName(), SavePaymentDetails() enthält. 

Spezialisierte Klassen wie RegularCustomer und GoldCustomer erben von der Customer-Basisklasse und implementieren ihre eigene CalculatePayment()- und CalculateRewardPoints()-Methodenlogik, verwenden jedoch die GetName()- und SavePaymentDetails()-Methoden erneut.

Sie können einer abstrakten Klasse (also nicht abstrakten Methoden) weitere Funktionen hinzufügen, ohne die untergeordneten Klassen zu beeinträchtigen, die eine ältere Version verwendet haben. Das Hinzufügen von Methoden zu einer Schnittstelle wirkt sich auf alle Klassen aus, die diese implementieren, da sie jetzt die neu hinzugefügten Schnittstellenmitglieder implementieren müssten.

Eine abstrakte Klasse mit allen abstrakten Elementen würde einer Schnittstelle ähneln.

35
PreethaA

Wann ist etwas sehr einfaches, wenn Sie das Konzept im Kopf haben. 

Abstrakte Klassen können abgeleitet werden, während Schnittstellen implementiert werden können. Es gibt einige Unterschiede zwischen den beiden. Wenn Sie eine abstrakte Klasse ableiten, ist die Beziehung zwischen der abgeleiteten Klasse und der Basisklasse eine 'Beziehung'. Ein Hund ist beispielsweise ein Tier, ein Schaf ist ein Tier, was bedeutet, dass eine abgeleitete Klasse einige Eigenschaften von der Basisklasse übernimmt.

Während für die Implementierung von Schnittstellen die Beziehung "kann sein" ist. Ein Hund kann beispielsweise ein Spionagehund sein. Ein Hund kann ein Zirkushund sein. Ein Hund kann ein Rennhund sein. Was bedeutet, dass Sie bestimmte Methoden implementieren, um etwas zu erwerben. 

Ich hoffe ich bin klar.

29
Aamir

Wenn Sie Java als OOP Sprache betrachten,

"Schnittstelle bietet keine Methodenimplementierung" ist beim Start von Java 8 nicht mehr gültig. Java bietet jetzt eine Implementierung in der Schnittstelle für Standardmethoden.

In einfachen Worten möchte ich verwenden

Schnittstelle: Zum Implementieren eines Vertrages durch mehrere nicht verbundene Objekte. Es bietet die Funktion "HAS A". 

abstrakte Klasse: Zum Implementieren des gleichen oder eines unterschiedlichen Verhaltens unter mehreren verwandten Objekten. Es stellt die Beziehung "IS A" her.

Oracle website bietet die wichtigsten Unterschiede zwischen der Klasse interface und der Klasse abstract.

Verwenden Sie abstrakte Klassen wenn:

  1. Sie möchten Code unter mehreren eng miteinander verwandten Klassen teilen.
  2. Sie erwarten, dass Klassen, die Ihre abstrakte Klasse erweitern, viele allgemeine Methoden oder Felder aufweisen oder andere Zugriffsmodifizierer als public (wie protected und private) erfordern.
  3. Sie möchten nicht statische oder nicht abschließende Felder deklarieren. 

Erwägen Sie die Verwendung von Schnittstellen wenn:

  1. Sie erwarten, dass nicht verknüpfte Klassen Ihre Schnittstelle implementieren. Beispielsweise können viele nicht zusammenhängende Objekte die Serializable-Schnittstelle implementieren.
  2. Sie möchten das Verhalten eines bestimmten Datentyps angeben, ohne sich jedoch darum zu kümmern, wer sein Verhalten implementiert.
  3. Sie möchten die Mehrfachvererbung des Typs nutzen.

Beispiel:

Abstrakte Klasse (IS A Relation)

Reader ist eine abstrakte Klasse.

BufferedReader ist eine Reader

FileReader ist eine Reader

FileReader und BufferedReader werden für allgemeine Zwecke verwendet: Lesen von Daten, und sie stehen in Beziehung zur Reader-Klasse.

Schnittstelle (HAS A - Fähigkeit)

Serializable ist eine Schnittstelle. 

Angenommen, Sie haben in Ihrer Anwendung zwei Klassen, die Serializable interface implementieren

Employee implements Serializable

Game implements Serializable

Hier kann keine Beziehung über die Serializable-Schnittstelle zwischen Employee und Game hergestellt werden, die für unterschiedliche Zwecke gedacht sind. Beide sind in der Lage, den Staat zu serialisieren und der Vergleich endet dort.

Schauen Sie sich diese Beiträge an:

Wie hätte ich den Unterschied zwischen einer Interface- und einer Abstract-Klasse erklären sollen?

27
Ravindra babu

Ich habe einen Artikel darüber geschrieben, wann eine abstrakte Klasse verwendet wird und wann eine Schnittstelle verwendet wird. Es gibt viel mehr Unterschiede zwischen ihnen als "ein IS-A ... und ein CAN-DO ...". Für mich sind das Dosenantworten. Ich erwähne einige Gründe, wann ich eines davon verwenden sollte. Ich hoffe es hilft.

http://codeofdoom.com/wordpress/2009/02/12/learn-this-wenn-zu-anwenden-anabstract-class-and-an-interface/

10
marcel

1.Wenn Sie etwas erstellen, das allgemeine Funktionen für nicht verwandte Klassen bietet, verwenden Sie eine Schnittstelle.

2. Wenn Sie etwas für Objekte erstellen, die in einer Hierarchie eng miteinander verbunden sind, verwenden Sie eine abstrakte Klasse.

10
kuttychutty

Ich denke, die prägnanteste Art, es auszudrücken, ist folgende:

Gemeinsame Eigenschaften => abstrakte Klasse.
Gemeinsame Funktionalität => Schnittstelle.

Und um es kurz zu fassen:.

Beispiel für eine abstrakte Klasse:  

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) { }
}

Da Tiere eine gemeinsame Eigenschaft haben - in diesem Fall die Anzahl der Beine -, ist es sinnvoll, eine abstrakte Klasse zu erstellen, die diese gemeinsame Eigenschaft enthält. Dies ermöglicht uns auch, allgemeinen Code zu schreiben, der für diese Eigenschaft ausgeführt wird. Zum Beispiel:

public static int CountAllLegs(List<BaseAnimal> animals)
{
    int legCount = 0;
    foreach (BaseAnimal animal in animals)
    {
        legCount += animal.NumberOfLegs;
    }
    return legCount;
}

Schnittstellenbeispiel:

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!");        
}

Hier ist zu beachten, dass Vuvuzelas und Autos völlig unterschiedliche Dinge sind, aber gemeinsame Funktionen haben: einen Ton erzeugen. Daher ist eine Schnittstelle hier sinnvoll. Außerdem können Programmierer Dinge, die Sounds erzeugen, unter einer gemeinsamen Schnittstelle zusammenfassen - in diesem Fall IMakeSound. Mit diesem Entwurf könnten Sie den folgenden Code schreiben:

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();
}

Kannst du sagen, was das ausgeben würde?

Schließlich können Sie die beiden kombinieren.

Kombiniertes Beispiel:

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!");
}

Hier benötigen wir alle BaseAnimals, die einen Sound erzeugen, aber wir kennen die Implementierung noch nicht. In diesem Fall können wir die Schnittstellenimplementierung abstrahieren und ihre Implementierung an ihre Unterklassen delegieren.

Ein letzter Punkt: Denken Sie daran, wie wir im abstrakten Klassenbeispiel die gemeinsamen Eigenschaften verschiedener Objekte bearbeiten konnten und im Schnittstellenbeispiel die gemeinsame Funktionalität verschiedener Objekte aufrufen konnten. In diesem letzten Beispiel könnten wir beides tun. 

4
Domn Werner

Erwägen Sie die Verwendung von abstract classes, wenn eine der folgenden Aussagen auf Ihre Situation zutrifft:

  1. Sie möchten Code unter mehreren eng miteinander verwandten Klassen teilen.
  2. Sie erwarten, dass Klassen, die Ihre abstrakte Klasse erweitern, viele allgemeine Methoden oder Felder aufweisen oder andere Zugriffsmodifizierer als public (wie protected und private) erfordern.
  3. Sie möchten nicht statische oder nicht abschließende Felder deklarieren. Auf diese Weise können Sie Methoden definieren, die auf den Status des Objekts, zu dem sie gehören, zugreifen und diesen ändern können.

Erwägen Sie die Verwendung von interfaces, wenn eine der folgenden Aussagen auf Ihre Situation zutrifft:

  1. Sie erwarten, dass nicht verknüpfte Klassen Ihre Schnittstelle implementieren. Zum Beispiel werden die Schnittstellen Comparable und Cloneable von vielen nicht verwandten Klassen implementiert.
  2. Sie möchten das Verhalten eines bestimmten Datentyps angeben, ohne sich jedoch darum zu kümmern, wer sein Verhalten implementiert.
  3. Sie möchten mehrere Vererbungen nutzen.

Quelle

Klassen können nur von einer Basisklasse erben. Wenn Sie abstrakte Klassen verwenden möchten, um einer Gruppe von Klassen Polymorphismus zu verleihen, müssen sie alle von dieser Klasse erben. Abstrakte Klassen können auch Mitglieder bereitstellen, die bereits implementiert wurden. Daher können Sie mit einer abstrakten Klasse eine gewisse identische Funktionalität gewährleisten, jedoch nicht mit einer Schnittstelle. 

Im Folgenden finden Sie einige Empfehlungen, mit denen Sie entscheiden können, ob Sie eine Schnittstelle oder eine abstrakte Klasse verwenden, um Polymorphie für Ihre Komponenten bereitzustellen.

  • Wenn Sie vorhaben, mehrere Versionen Ihrer Komponente zu erstellen, erstellen Sie eine abstrakte Klasse. Abstrakte Klassen bieten eine einfache und einfache Möglichkeit, Ihre Komponenten zu versionieren. Durch die Aktualisierung der Basisklasse werden alle erbenden Klassen automatisch mit der Änderung aktualisiert. Schnittstellen dagegen können nicht mehr geändert werden, wenn sie auf diese Weise erstellt wurden. Wenn eine neue Version einer Schnittstelle erforderlich ist, müssen Sie eine vollständige neue Schnittstelle erstellen. 
  • Wenn die von Ihnen erstellte Funktionalität für eine Vielzahl unterschiedlicher Objekte nützlich ist, verwenden Sie eine Schnittstelle. Abstrakte Klassen sollten in erster Linie für Objekte verwendet werden, die eng miteinander verwandt sind. Schnittstellen eignen sich jedoch am besten für die Bereitstellung allgemeiner Funktionen für nicht verwandte Klassen . 
  • Wenn Sie kleine, prägnante Funktionen entwerfen, verwenden Sie Schnittstellen. Wenn Sie große Funktionseinheiten entwerfen, verwenden Sie eine abstrakte Klasse . 
  • Wenn Sie unter allen Implementierungen Ihrer Komponente eine gemeinsame, implementierte Funktionalität bereitstellen möchten, verwenden Sie eine abstrakte Klasse. Mit abstrakten Klassen können Sie Ihre Klasse teilweise implementieren, während Schnittstellen keine Implementierung für Mitglieder enthalten.

Kopiert von:
http://msdn.Microsoft.com/de-de/library/scsyfw1d%28v=vs.71%29.aspx

3
Mazhar

Für mich würde ich in vielen Fällen mit Schnittstellen gehen. In manchen Fällen bevorzuge ich abstrakten Unterricht.

Klassen in OO beziehen sich allgemein auf die Implementierung. Ich verwende abstrakte Klassen, wenn ich den Childs einige Implementierungsdetails erzwingen möchte.

Abstrakte Klassen sind natürlich nicht nur nützlich, um die Implementierung zu erzwingen, sondern auch um bestimmte Details zwischen vielen verwandten Klassen zu teilen.

2
La VloZ Merrill

Die Antworten variieren zwischen den Sprachen. In Java kann eine Klasse beispielsweise mehrere Schnittstellen implementieren (erben), aber nur von einer abstrakten Klasse erben. Schnittstellen geben Ihnen also mehr Flexibilität. Dies gilt jedoch nicht für C++.

2
Nick Fortescue

Verwenden Sie eine abstrakte Klasse, wenn Sie einige grundlegende Implementierungen bereitstellen möchten.

1
Sebastian

in Java können Sie von einer (abstrakten) Klasse erben, um Funktionalität bereitzustellen, und Sie können viele Schnittstellen implementieren, um die Funktionalität zu gewährleisten

1
Peter Miehle

Ein interessanter Ort, an dem Schnittstellen besser als abstrakte Klassen abschneiden, ist das Hinzufügen zusätzlicher Funktionen zu einer Gruppe von (verwandten oder nicht verknüpften) Objekten. Wenn Sie ihnen keine abstrakte Basisklasse geben können (z. B. sealed oder bereits übergeordnet), können Sie ihnen stattdessen eine leere (leere) Schnittstelle zuweisen und dann einfach Erweiterungsmethoden für diese Schnittstelle schreiben.

1
Dmitri Nesteruk

Nur auf der Grundlage der Vererbung würden Sie eine Zusammenfassung verwenden, in der Sie eindeutig Nachkommenschaft definieren, abstrakte Beziehungen definieren (z. B. Tier-> Katze) und/oder die Vererbung von virtuellen oder nicht öffentlichen Eigenschaften, insbesondere des gemeinsamen Zustands, erfordern ( welche Interfaces nicht unterstützen können).

Sie sollten versuchen, die Komposition (über Abhängigkeitseinspritzung) gegenüber der Vererbung zu bevorzugen, sofern dies möglich ist, und beachten Sie, dass Interfaces als Verträge Unit-Tests, Aufgabentrennung und (sprachverändernde) Mehrfachvererbung auf eine Weise unterstützen, die Abstracts nicht bieten.

1
annakata

Wann eine abstrakte Klasse der Schnittstelle vorziehen?

  1. Wenn Sie planen, eine Basisklasse während der gesamten Lebensdauer eines Programms/Projekts zu aktualisieren, ist es am besten, wenn die Basisklasse eine abstrakte Klasse sein soll
  2. Wenn man versucht, ein Rückgrat für Objekte zu schaffen, die in einer Hierarchie eng miteinander verbunden sind, ist es von großem Vorteil, eine abstrakte Klasse zu verwenden

Wann eine Schnittstelle der abstrakten Klasse vorziehen?

  1. Wenn man sich nicht mit einem massiven hierarchischen Rahmen beschäftigt, wären Schnittstellen eine gute Wahl
  2. Da Mehrfachvererbung bei abstrakten Klassen nicht unterstützt wird (Rautenproblem), können Schnittstellen den Tag retten 
1
Satya

Dies kann ein sehr schwieriger Anruf sein ...

Einen Zeiger kann ich geben: Ein Objekt kann viele Schnittstellen implementieren, während ein Objekt nur eine Basisklasse erben kann (in einer modernen OO - Sprache wie c # weiß ich, dass C++ mehrere Vererbung hat - aber ist das nicht verpönt? )

0
Mesh

Eine abstrakte Klasse kann Implementierungen haben.

Eine Schnittstelle hat keine Implementierungen, sie definiert lediglich eine Art von Vertrag.

Es kann auch einige sprachabhängige Unterschiede geben: Beispielsweise hat C # keine Mehrfachvererbung, es können jedoch mehrere Schnittstellen in einer Klasse implementiert werden.

0
Gerrie Schenck