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?
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.
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
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:
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.
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.
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.
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.
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:
Erwägen Sie die Verwendung von Schnittstellen wenn:
Serializable
-Schnittstelle implementieren.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?
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.
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.
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 BaseAnimal
s, 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.
Erwägen Sie die Verwendung von abstract classes, wenn eine der folgenden Aussagen auf Ihre Situation zutrifft:
Erwägen Sie die Verwendung von interfaces, wenn eine der folgenden Aussagen auf Ihre Situation zutrifft:
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.
Kopiert von:
http://msdn.Microsoft.com/de-de/library/scsyfw1d%28v=vs.71%29.aspx
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.
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++.
Verwenden Sie eine abstrakte Klasse, wenn Sie einige grundlegende Implementierungen bereitstellen möchten.
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
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.
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.
Wann eine abstrakte Klasse der Schnittstelle vorziehen?
Wann eine Schnittstelle der abstrakten Klasse vorziehen?
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? )
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.