Quelle : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/
C# ist eine wunderbare Sprache, die in den letzten 14 Jahren gereift ist und sich weiterentwickelt hat. Das ist großartig für uns Entwickler, denn eine ausgereifte Sprache bietet uns eine Fülle von Sprachfunktionen, die uns zur Verfügung stehen.
Doch mit viel Macht geht auch viel Verantwortung einher. Einige dieser Funktionen können missbraucht werden, oder manchmal ist es schwer zu verstehen, warum man eine Funktion einer anderen vorziehen sollte. Im Laufe der Jahre habe ich gesehen, dass viele Entwickler mit der Frage ringen, wann sie eine Schnittstelle oder eine abstrakte Klasse verwenden sollten. Beide haben ihre Vor- und Nachteile und die richtige Zeit und den richtigen Ort für ihre Verwendung. Aber wie soll man sich entscheiden???
Beide ermöglichen die Wiederverwendung gemeinsamer Funktionen zwischen Typen. Der offensichtlichste Unterschied besteht darin, dass Schnittstellen keine Implementierung ihrer Funktionalität bieten, während abstrakte Klassen die Möglichkeit bieten, ein "Basis"- oder "Standard"-Verhalten zu implementieren und dann die Möglichkeit haben, dieses Standardverhalten mit den abgeleiteten Typen der Klasse zu "überschreiben", falls erforderlich.
Das ist alles schön und gut und sorgt für eine großartige Wiederverwendung von Code und hält sich an das DRY-Prinzip (Don't Repeat Yourself) der Softwareentwicklung. Abstrakte Klassen sind hervorragend geeignet, wenn Sie eine "ist ein"-Beziehung haben.
Zum Beispiel: Ein Golden Retriever ist eine" Art von Hund. Das gilt auch für einen Pudel. Sie können beide bellen, wie alle Hunde. Sie könnten jedoch angeben, dass der Pudelpark sich deutlich vom "Standard"-Hundebellen unterscheidet. Daher könnte es für Sie sinnvoll sein, etwas wie folgt zu implementieren:
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
//
Wie Sie sehen können, wäre dies ein großartiger Weg, um Ihren Code DRY zu halten und ermöglichen für die Basisklasse Implementierung aufgerufen werden, wenn einer der Typen kann nur auf die Standard-Bark anstelle eines Sonderfalls Implementierung verlassen. Die Klassen wie GoldenRetriever, Boxer, Lab könnten alle das "Standard" (Basisklasse) Bark kostenlos erben, nur weil sie die abstrakte Klasse Dog implementieren.
Aber ich bin sicher, das wussten Sie schon.
Sie sind hier, weil Sie verstehen wollen, warum Sie eine Schnittstelle einer abstrakten Klasse vorziehen sollten oder umgekehrt. Ein Grund, warum Sie eine Schnittstelle einer abstrakten Klasse vorziehen sollten, ist, wenn Sie keine Standardimplementierung haben oder diese verhindern wollen. Dies liegt in der Regel daran, dass die Typen, die die Schnittstelle implementieren, nicht in einer "ist ein"-Beziehung zueinander stehen. Eigentlich müssen sie überhaupt nicht miteinander verwandt sein, außer der Tatsache, dass jeder Typ "in der Lage" ist oder "die Fähigkeit" hat, etwas zu tun oder etwas zu haben.
Was zum Teufel bedeutet das nun? Nun, zum Beispiel: Ein Mensch ist keine Ente und eine Ente ist kein Mensch. Ziemlich offensichtlich. Aber sowohl eine Ente als auch ein Mensch haben "die Fähigkeit" zu schwimmen (vorausgesetzt, der Mensch hat seinen Schwimmunterricht in der ersten Klasse bestanden :) ). Da eine Ente kein Mensch ist und umgekehrt, handelt es sich nicht um eine "ist ein"-Beziehung, sondern um eine "kann"-Beziehung, und wir können eine Schnittstelle verwenden, um dies zu veranschaulichen:
// 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!
Die Verwendung von Schnittstellen wie im obigen Code ermöglicht es Ihnen, ein Objekt an eine Methode zu übergeben, die etwas tun "kann". Der Code kümmert sich nicht darum, wie er es tut Er weiß nur, dass er die Swim-Methode für dieses Objekt aufrufen kann und dass das Objekt weiß, welches Verhalten es zur Laufzeit aufgrund seines Typs annehmen wird.
Auch dies trägt dazu bei, dass Ihr Code trocken bleibt, so dass Sie nicht mehrere Methoden schreiben müssen, die das Objekt aufrufen, um dieselbe Kernfunktion auszuführen (ShowHowHumanSwims(human), ShowHowDuckSwims(duck), usw.).
Die Verwendung einer Schnittstelle ermöglicht es den aufrufenden Methoden, sich nicht darum zu kümmern, welcher Typ welcher ist oder wie das Verhalten implementiert wird. Sie weiß nur, dass angesichts der Schnittstelle jedes Objekt die Swim-Methode implementiert haben muss, so dass es sicher ist, sie in seinem eigenen Code aufzurufen und zuzulassen, dass das Verhalten der Swim-Methode innerhalb seiner eigenen Klasse behandelt wird.
Zusammenfassung:
Meine wichtigste Faustregel lautet also: Verwenden Sie eine abstrakte Klasse, wenn Sie eine "Standard"-Funktionalität für eine Klassenhierarchie implementieren möchten oder/und wenn die Klassen oder Typen, mit denen Sie arbeiten, eine "ist ein"-Beziehung aufweisen (z. B. Pudel "ist ein" Typ von Hund).
Andererseits verwenden Sie eine Schnittstelle, wenn Sie keine "ist ein"-Beziehung haben, sondern Typen, die "die Fähigkeit" haben, etwas zu tun oder zu haben (z. B. Ente "ist nicht" ein Mensch. Ente und Mensch teilen sich jedoch die "Fähigkeit" zu schwimmen).
Ein weiterer Unterschied zwischen abstrakten Klassen und Schnittstellen besteht darin, dass eine Klasse eine oder mehrere Schnittstellen implementieren kann, aber eine Klasse kann nur von EINER abstrakten Klasse (oder einer beliebigen Klasse) erben. Ja, man kann Klassen verschachteln und eine Vererbungshierarchie haben (was viele Programme tun und haben sollten), aber man kann nicht zwei Klassen in einer abgeleiteten Klassendefinition erben (diese Regel gilt für C#. In einigen anderen Sprachen ist dies möglich, normalerweise aber nur, weil es in diesen Sprachen keine Schnittstellen gibt).
Denken Sie auch daran, bei der Verwendung von Schnittstellen das Prinzip der Schnittstellentrennung (ISP) einzuhalten. Das ISP besagt, dass kein Client gezwungen werden sollte, von Methoden abhängig zu sein, die er nicht verwendet. Aus diesem Grund sollten sich Schnittstellen auf bestimmte Aufgaben konzentrieren und in der Regel sehr klein sein (z. B. IDisposable, IComparable).
Ein weiterer Tipp ist die Verwendung von Schnittstellen, wenn Sie kleine, prägnante Funktionseinheiten entwickeln. Wenn Sie große Funktionseinheiten entwickeln, verwenden Sie eine abstrakte Klasse.
Ich hoffe, das klärt die Sache für einige Leute auf!
Wenn Ihnen noch bessere Beispiele einfallen oder Sie auf etwas hinweisen möchten, schreiben Sie es bitte in die Kommentare unten!
11 Stimmen
Nur ein Punkt, den Sie meiner Meinung nach berücksichtigen sollten - Schnittstellen können verschiedene Einschränkungen mit sich bringen, derer Sie sich möglicherweise erst in sehr späten Phasen bewusst sind. Zum Beispiel mit .NET können Sie nicht serialisieren eine Schnittstelle Mitglied Variable, so dass, wenn Sie eine Klasse Zoo und ein Mitglied Variable Array von IAnimals haben Sie nicht in der Lage, Zoo serialisieren (und das bedeutet, schreiben WebServices oder andere Dinge, die eine Serialisierung wäre ein Schmerz).
2 Stimmen
Diese Frage könnte helfen, das Konzept der Schnittstellen zu verstehen. stackoverflow.com/q/8531292/1055241
0 Stimmen
Ich bin nur neugierig. Ich traf in der CLR über C# den folgenden Auszug:
I tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation.
. Ich kann nicht begreifen, was in dem Auszug gemeint ist. Wir können einige Basistypen erstellen und für jeden von ihnen einen abgeleiteten Typ erstellen, so dass ein Entwickler einen Basistyp auswählen kann. Könnte mir bitte jemand erklären, was ich übersehe? Ich glaube, es kann ein Teil dieser Frage sein. Oder sollte ich eine andere Frage zu dem spezifischen Auszug stellen?