Im Allgemeinen lautet die Regel ungefähr so:
- Vererbung beschreibt eine Beziehung.
- Das Implementieren einer Schnittstelle beschreibt eine Can-Do- Beziehung.
Um dies etwas konkreter auszudrücken, schauen wir uns ein Beispiel an. Die System.Drawing.Bitmap
Klasse ist-ein Bild (und als solche, es erbt von der Image
Klasse), aber es auch Can-do entsorgen, so dass es die implementiert IDisposable
Schnittstelle. Es kann auch Serialisierung durchführen, sodass es über die ISerializable
Schnittstelle implementiert wird.
In der Praxis werden Schnittstellen jedoch häufig verwendet, um die Mehrfachvererbung in C # zu simulieren. Wenn Ihre Processor
Klasse von so etwas erben muss System.ComponentModel.Component
, haben Sie keine andere Wahl, als eine IProcessor
Schnittstelle zu implementieren .
Tatsache ist, dass sowohl Schnittstellen als auch abstrakte Basisklassen einen Vertrag bereitstellen, der angibt, was eine bestimmte Klasse tun kann. Es ist ein weit verbreiteter Mythos, dass Schnittstellen erforderlich sind, um diesen Vertrag zu deklarieren, aber das ist nicht korrekt. Der größte Vorteil für mich ist, dass Sie mit abstrakten Basisklassen Standardfunktionen für die Unterklassen bereitstellen können. Wenn es jedoch keine sinnvolle Standardfunktionalität gibt, hindert Sie nichts daran, die Methode selbst als zu abstract
kennzeichnen, sodass abgeleitete Klassen sie selbst implementieren müssen, genau wie wenn sie eine Schnittstelle implementieren würden.
Für Antworten auf Fragen wie diese wende ich mich häufig den .NET Framework-Entwurfsrichtlinien zu , die Folgendes zur Auswahl zwischen Klassen und Schnittstellen enthalten:
Im Allgemeinen sind Klassen das bevorzugte Konstrukt zum Offenlegen von Abstraktionen.
Der Hauptnachteil von Schnittstellen besteht darin, dass sie viel weniger flexibel als Klassen sind, wenn es darum geht, die Entwicklung von APIs zu ermöglichen. Sobald Sie eine Schnittstelle versenden, ist die Anzahl ihrer Mitglieder für immer festgelegt. Alle Ergänzungen der Schnittstelle würden vorhandene Typen, die die Schnittstelle implementieren, beschädigen.
Eine Klasse bietet viel mehr Flexibilität. Sie können Mitglieder zu Klassen hinzufügen, die Sie bereits ausgeliefert haben. Solange die Methode nicht abstrakt ist (dh solange Sie eine Standardimplementierung der Methode bereitstellen), funktionieren vorhandene abgeleitete Klassen weiterhin unverändert.
[. . . ]]
Eines der häufigsten Argumente für Schnittstellen ist, dass sie die Trennung von Vertrag und Implementierung ermöglichen. Das Argument geht jedoch fälschlicherweise davon aus, dass Sie Verträge nicht mithilfe von Klassen von der Implementierung trennen können. Abstrakte Klassen, die sich in einer von ihren konkreten Implementierungen getrennten Baugruppe befinden, sind eine gute Möglichkeit, eine solche Trennung zu erreichen.
Ihre allgemeinen Empfehlungen lauten wie folgt:
- Sie favorisieren Klassen über Schnittstellen zu definieren.
- Sie verwenden abstrakte Klassen statt Schnittstellen den Vertrag von Implementierungen zu entkoppeln. Abstrakte Klassen ermöglichen bei korrekter Definition den gleichen Grad an Entkopplung zwischen Vertrag und Implementierung.
- Sie eine Schnittstelle definieren , wenn Sie eine polymorphe Hierarchie von Werttypen zur Verfügung stellen müssen.
- Ziehen Sie in Betracht , Schnittstellen zu definieren, um einen ähnlichen Effekt wie bei der Mehrfachvererbung zu erzielen.
Chris Anderson stimmt diesem letzten Grundsatz besonders zu und argumentiert:
Abstrakte Typen machen die Version viel besser und ermöglichen zukünftige Erweiterbarkeit, aber sie brennen auch Ihren einzigen Basistyp. Schnittstellen sind geeignet, wenn Sie tatsächlich einen Vertrag zwischen zwei Objekten definieren, der über die Zeit unveränderlich ist. Abstrakte Basistypen eignen sich besser zum Definieren einer gemeinsamen Basis für eine Typenfamilie.