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 und weiterentwickelt wurde. Dies ist großartig für uns Entwickler, da eine ausgereifte Sprache uns eine Vielzahl von Sprachfunktionen bietet, die uns zur Verfügung stehen.
Mit viel Kraft wird jedoch viel Verantwortung. Einige dieser Funktionen können missbraucht werden, oder manchmal ist es schwer zu verstehen, warum Sie eine Funktion einer anderen vorziehen. Im Laufe der Jahre haben viele Entwickler Probleme damit, sich für die Verwendung einer Schnittstelle oder einer abstrakten Klasse zu entscheiden. Beide haben dort Vor- und Nachteile und die richtige Zeit und den richtigen Ort, um sie zu nutzen. Aber wie entscheiden wir uns ???
Beide ermöglichen die Wiederverwendung gemeinsamer Funktionen zwischen Typen. Der offensichtlichste Unterschied besteht sofort darin, dass Schnittstellen keine Implementierung für ihre Funktionalität bieten, während abstrakte Klassen es Ihnen ermöglichen, ein "Basis" - oder "Standard" -Verhalten zu implementieren und dieses Standardverhalten dann bei Bedarf mit den von Klassen abgeleiteten Typen zu "überschreiben" .
Dies ist alles gut und schön und ermöglicht eine hervorragende Wiederverwendung von Code und entspricht dem DRY-Prinzip (Don't Repeat Yourself) der Softwareentwicklung. Abstrakte Klassen eignen sich hervorragend, wenn Sie eine "is a" -Beziehung haben.
Zum Beispiel: Ein Golden Retriever ist eine Art Hund. So ist ein Pudel. Sie können beide bellen, wie alle Hunde. Möglicherweise möchten Sie jedoch angeben, dass sich der Pudelpark erheblich von der „Standard“ -Hunderinde unterscheidet. Daher kann es für Sie sinnvoll sein, Folgendes 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, ist dies eine großartige Möglichkeit, Ihren Code DRY zu halten und den Aufruf der Basisklassenimplementierung zu ermöglichen, wenn sich einer der Typen nur auf die Standard-Bark anstelle einer Spezialfallimplementierung verlassen kann. Die Klassen wie GoldenRetriever, Boxer, Lab konnten alle die "Standard" -Rinde (Bassklasse) kostenlos erben, nur weil sie die abstrakte Klasse "Hund" implementieren.
Aber ich bin sicher, dass du das schon wusstest.
Sie sind hier, weil Sie verstehen möchten, warum Sie eine Schnittstelle einer abstrakten Klasse vorziehen möchten oder umgekehrt. Ein Grund, warum Sie eine Schnittstelle einer abstrakten Klasse vorziehen möchten, ist, dass Sie keine Standardimplementierung haben oder verhindern möchten. Dies liegt normalerweise daran, dass die Typen, die die Schnittstelle implementieren, nicht in einer "is a" -Beziehung zusammenhängen. Eigentlich müssen sie überhaupt nicht verwandt sein, außer dass jeder Typ "fähig" ist oder "die Fähigkeit" hat, etwas zu tun oder etwas zu haben.
Was zum Teufel bedeutet das? Zum Beispiel: Ein Mensch ist keine Ente… und eine Ente ist kein Mensch. Ganz schön offensichtlich. Sowohl eine Ente als auch ein Mensch haben jedoch „die Fähigkeit“ zu schwimmen (vorausgesetzt, der Mensch hat seinen Schwimmunterricht in der 1. Klasse bestanden :)). Da eine Ente kein Mensch ist oder umgekehrt, handelt es sich nicht um eine „Ist“ -Realität, sondern um eine „Ist“ -Beziehung, und wir können eine Schnittstelle verwenden, um Folgendes 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!
Wenn Sie Schnittstellen wie den obigen Code verwenden, können Sie ein Objekt an eine Methode übergeben, die etwas „kann“. Dem Code ist es egal, wie er es macht ... Er weiß nur, dass er die Swim-Methode für dieses Objekt aufrufen kann und dieses Objekt anhand seines Typs weiß, welches Verhalten zur Laufzeit ausgeführt wird.
Dies hilft Ihrem Code erneut, trocken zu bleiben, sodass Sie nicht mehrere Methoden schreiben müssen, die das Objekt aufrufen, um dieselbe Kernfunktion auszuführen (ShowHowHumanSwims (Mensch), ShowHowDuckSwims (Ente) usw.).
Durch die Verwendung einer Schnittstelle können sich die aufrufenden Methoden keine Gedanken darüber machen, welcher Typ welcher ist oder wie das Verhalten implementiert wird. Es ist nur bekannt, dass angesichts der Schnittstelle jedes Objekt die Swim-Methode implementiert haben muss, damit es sicher in seinem eigenen Code aufgerufen werden kann und das Verhalten der Swim-Methode in seiner eigenen Klasse behandelt werden kann.
Zusammenfassung:
Meine Faustregel lautet daher, eine abstrakte Klasse zu verwenden, wenn Sie eine "Standard" -Funktionalität für eine Klassenhierarchie implementieren möchten oder / und die Klassen oder Typen, mit denen Sie arbeiten, eine "is a" -Beziehung teilen (z. B. pudel "is a" Hundetyp).
Verwenden Sie andererseits eine Schnittstelle, wenn Sie keine "ist eine" Beziehung haben, sondern Typen haben, die "die Fähigkeit" teilen, etwas zu tun oder etwas zu haben (z. B. "Ente ist kein 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 bis mehrere Schnittstellen implementieren kann, eine Klasse jedoch nur von EINER abstrakten Klasse (oder einer anderen Klasse) erben kann. Ja, Sie können Klassen verschachteln und eine Vererbungshierarchie haben (was viele Programme tun und haben sollten), aber Sie können nicht zwei Klassen in einer abgeleiteten Klassendefinition erben (diese Regel gilt für C #. In einigen anderen Sprachen können Sie dies normalerweise tun nur wegen des Fehlens von Schnittstellen in diesen Sprachen).
Denken Sie auch daran, wenn Sie Schnittstellen verwenden, um das Interface Segregation Principle (ISP) einzuhalten. Der ISP gibt an, dass kein Client gezwungen werden sollte, sich auf Methoden zu verlassen, die er nicht verwendet. Aus diesem Grund sollten sich die Schnittstellen auf bestimmte Aufgaben konzentrieren und sind normalerweise sehr klein (z. B. IDisposable, IComparable).
Ein weiterer Tipp ist, wenn Sie kleine, prägnante Funktionen entwickeln, Schnittstellen zu verwenden. Wenn Sie große Funktionseinheiten entwerfen, verwenden Sie eine abstrakte Klasse.
Hoffe, das klärt die Dinge für einige Leute auf!
Auch wenn Sie sich bessere Beispiele vorstellen oder auf etwas hinweisen möchten, tun Sie dies bitte in den Kommentaren unten!