Wann werden abstrakte Klassen anstelle von Schnittstellen mit Erweiterungsmethoden in C # verwendet?


70

"Abstrakte Klasse" und "Schnittstelle" sind ähnliche Konzepte, wobei die Schnittstelle die abstraktere der beiden ist. Ein entscheidender Faktor ist, dass abstrakte Klassen bei Bedarf Methodenimplementierungen für abgeleitete Klassen bereitstellen. In C # wurde dieser Differenzierungsfaktor jedoch durch die kürzliche Einführung von Erweiterungsmethoden reduziert, die es ermöglichen, Implementierungen für Schnittstellenmethoden bereitzustellen. Ein weiterer Unterscheidungsfaktor ist, dass eine Klasse nur eine abstrakte Klasse erben kann (dh es gibt keine Mehrfachvererbung), aber sie kann mehrere Schnittstellen implementieren. Dies macht Schnittstellen weniger restriktiv und flexibler. Wann sollten wir in C # abstrakte Klassen anstelle von Schnittstellen mit Erweiterungsmethoden verwenden?

Ein bemerkenswertes Beispiel für das Modell der Schnittstelle + Erweiterungsmethode ist LINQ, bei dem die Abfragefunktion für jeden Typ bereitgestellt wird, der IEnumerableüber eine Vielzahl von Erweiterungsmethoden implementiert wird .


2
Und wir können 'Properties' auch in Interfaces verwenden.
Gulshan

1
Klingt eher nach einer C # -spezifischen als nach einer allgemeinen OOD-Frage. (Die meisten anderen OO-Sprachen haben weder Erweiterungsmethoden noch Eigenschaften.)
Péter Török


4
Erweiterungsmethoden lösen das Problem, das die abstrakten Klassen lösen, nicht ganz. Der Grund ist also nicht anders als in Java oder einer anderen ähnlichen Sprache mit einfacher Vererbung.
Job

3
Der Bedarf an Methodenimplementierungen für abstrakte Klassen wurde durch Erweiterungsmethoden nicht verringert. Erweiterungsmethoden können weder auf private Mitgliedsfelder zugreifen, noch können sie als virtuell deklariert und in abgeleiteten Klassen überschrieben werden.
Zooone9243

Antworten:


63

Wenn ein Teil der Klasse implementiert werden muss. Das beste Beispiel, das ich verwendet habe, ist das Template-Methodenmuster .

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

Auf diese Weise können Sie die Schritte definieren, die beim Aufrufen von Do () ausgeführt werden, ohne die Einzelheiten der Implementierung zu kennen. Ableitende Klassen müssen die abstrakten Methoden implementieren, nicht jedoch die Do () -Methode.

Erweiterungsmethoden erfüllen nicht unbedingt den Teil der Gleichung, der ein Teil der Klasse sein muss. Darüber hinaus können (scheinen) iirc-Erweiterungsmethoden in ihrem Gültigkeitsbereich alles andere als öffentlich sein.

bearbeiten

Die Frage ist interessanter, als ich ursprünglich angenommen hatte. Nach weiterer Prüfung beantwortete Jon Skeet eine solche Frage zu SO zugunsten der Verwendung von Schnittstellen + Erweiterungsmethoden. Ein möglicher Nachteil ist auch die Reflexion gegen eine auf diese Weise entworfene Objekthierarchie.

Persönlich habe ich Probleme, den Nutzen einer Änderung einer derzeit üblichen Praxis zu erkennen, sehe aber auch einige bis gar keine Nachteile, wenn ich das tue.

Es ist zu beachten, dass es über Utility-Klassen möglich ist, auf diese Weise in vielen Sprachen zu programmieren. Erweiterungen stellen nur den syntaktischen Zucker bereit, damit die Methoden so aussehen, als ob sie zur Klasse gehören.


Schlagen Sie mich zu ..
Giorgi

1
Dasselbe kann jedoch mit Schnittstellen und Erweiterungsmethoden geschehen. Dort werden die Methoden, die Sie in der abstrakten Basisklasse Do()definieren, als Erweiterungsmethode definiert. Und die Methoden, die für die Definition der abgeleiteten Klassen übrig DoThis()bleiben, werden Mitglied der Hauptschnittstelle. Und mit Schnittstellen können Sie mehrere Implementierungen / Vererbungen unterstützen. Und siehe dies- odetocode.com/blogs/scott/archive/2009/10/05/…
Gulshan

@ Gulshan: Weil eine Sache auf mehr als eine Weise getan werden kann, macht sie den gewählten Weg nicht ungültig. Die Verwendung von Schnittstellen bietet keinen Vorteil. Die abstrakte Klasse selbst ist die Schnittstelle. Sie benötigen keine Schnittstellen, um mehrere Implementierungen zu unterstützen.
ThomasX

Das Interface + Extension-Modell kann auch Nachteile haben. Nehmen Sie zum Beispiel die Linq-Bibliothek. Es ist großartig, aber wenn Sie es nur einmal in dieser Codedatei verwenden müssen, ist die vollständige Linq-Bibliothek in IntelliSense auf JEDEM IE verfügbar, der in Ihrer Codedatei nicht enthalten ist. Dies kann die IDE-Leistung erheblich verlangsamen.
KeithS

-1, weil Sie nicht gezeigt haben, in welcher Weise abstrakte Klassen besser für die Template-Methode geeignet sind als Schnittstellen
Benjamin Hodgson

25

Es geht darum, was Sie modellieren möchten:

Abstrakte Klassen arbeiten durch Vererbung . Da sie nur spezielle Basisklassen sind, modellieren sie eine Beziehung.

Zum Beispiel ein Hund ist ein Tier, so haben wir

class Dog : Animal { ... }

Da es keinen Sinn macht , ein generisches All-Tier zu erstellen (wie würde es aussehen?), Machen wir diese AnimalBasisklasse abstract- aber es ist immer noch eine Basisklasse. Und die DogKlasse ergibt keinen Sinn, ohne eine zu sein Animal.

Schnittstellen sind eine andere Geschichte. Sie verwenden keine Vererbung, sondern bieten Polymorphismus (der auch mit Vererbung implementiert werden kann ). Sie modellieren keine Is -A- Beziehung, sondern eher eine, die sie unterstützt .

Nehmen wir zum Beispiel IComparable- ein Objekt, das es unterstützt, mit einem anderen verglichen zu werden .

Die Funktionalität einer Klasse hängt nicht von den von ihr implementierten Schnittstellen ab. Die Schnittstelle bietet lediglich eine allgemeine Möglichkeit, auf die Funktionalität zuzugreifen. Wir könnten noch Disposeeine Graphicsoder eine FileStreamohne sie umsetzen IDisposable. Die Schnittstelle verknüpft nur die Methoden miteinander.

Im Prinzip kann man Schnittstellen wie Erweiterungen hinzufügen und entfernen, ohne das Verhalten einer Klasse zu ändern, und so den Zugriff darauf verbessern. Sie können jedoch keine Basisklasse entfernen, da das Objekt bedeutungslos werden würde!


Wann möchten Sie eine Basisklasse abstrakt halten?
Gulshan

1
@Gulshan: Wenn es aus irgendeinem Grund nicht sinnvoll ist, eine Instanz der Basisklasse zu erstellen. Sie können einen Chihuahua oder einen weißen Hai "erschaffen", aber Sie können kein Tier ohne weitere Spezialisierung erschaffen - also würden Sie Animalabstrakt machen . Auf diese Weise können Sie abstractFunktionen schreiben , die auf die abgeleiteten Klassen spezialisiert sind. Oder mehr in Bezug auf die Programmierung - es spielt wohl keinen Sinn machen , eine zu schaffen UserControl, sondern als eine Button ist ein UserControl, so haben wir die gleiche Art von Klasse / Basisklasse Beziehung.
Dario

Wenn Sie abstrakte Methoden haben, dann haben Sie eine abstrakte Klasse. Und abstrakte Methoden, die Sie haben, wenn es keinen Wert für die Implementierung gibt (wie "go" für animal). Und natürlich möchten Sie manchmal Objekte einer allgemeinen Klasse nicht zulassen, als Sie es abstrakt machen.
Dainius

Es gibt keine Regel, die besagt, dass A eine abstrakte Klasse sein muss, wenn es grammatisch und semantisch bedeutsam ist, zu sagen, dass B ein A ist. Sie sollten Schnittstellen bevorzugen, bis Sie die Klasse wirklich nicht vermeiden können. Das Teilen von Code kann über die Zusammenstellung von Schnittstellen usw. erfolgen. Dadurch wird es einfacher, sich nicht selbst in eine Ecke mit seltsamen Vererbungsbäumen zu malen.
Sara

3

Mir ist gerade aufgefallen, dass ich seit Jahren keine abstrakten Klassen mehr verwendet habe (zumindest keine).

Die abstrakte Klasse ist ein merkwürdiges Biest zwischen Schnittstelle und Implementierung. Es gibt etwas in abstrakten Klassen, das meinen Spinnensinn kribbelt. Ich würde argumentieren, man sollte die Implementierung hinter der Schnittstelle in keiner Weise "blenden" (oder irgendwelche Verknüpfungen herstellen).

Selbst wenn Klassen, die eine Schnittstelle implementieren, ein gemeinsames Verhalten benötigen, würde ich statt einer abstrakten Klasse eine unabhängige Hilfsklasse verwenden.

Ich würde für Schnittstellen gehen.


2
-1: Ich bin mir nicht sicher, wie alt Sie sind, aber Sie möchten vielleicht Entwurfsmuster lernen, insbesondere das Muster der Vorlagenmethode. Design Patterns machen Sie zu einem besseren Programmierer und beenden Ihre Unkenntnis von abstrakten Klassen.
Jim G.

Gleiches gilt für das Kribbeln. Klassen, die erste und wichtigste Funktion von C # und Java, sind eine Funktion zum Erstellen eines Typs und zum sofortigen Verketten dieses Typs für immer an eine Implementierung.
Jesse Millikan

2

IMHO, ich denke, dass es hier eine Vermischung von Konzepten gibt.

Klassen verwenden eine bestimmte Art von Vererbung, während Schnittstellen eine andere Art von Vererbung verwenden.

Beide Arten der Vererbung sind wichtig und nützlich, und jede hat ihre Vor- und Nachteile.

Beginnen wir am Anfang.

Die Geschichte mit Interfaces beginnt schon lange mit C ++. Mitte der 1980er Jahre, als C ++ entwickelt wurde, wurde die Idee von Schnittstellen als eine andere Art von Typ noch nicht verwirklicht (sie würde rechtzeitig kommen).

C ++ hatte nur eine Art von Vererbung, die Art der von Klassen verwendeten Vererbung, die als Implementierungsvererbung bezeichnet wird.

Um die GoF Book-Empfehlung anwenden zu können: "Für die Schnittstelle und nicht für die Implementierung programmieren", unterstützte C ++ abstrakte Klassen und Mehrfachimplementierungsvererbung, um in der Lage zu sein, zu tun, wozu Schnittstellen in C # in der Lage (und nützlich!) Sind .

C # führt eine neue Art von Typ, Schnittstellen und eine neue Art von Vererbung, die Schnittstellenvererbung, ein, um mindestens zwei verschiedene Möglichkeiten zur Unterstützung von "So programmieren Sie für die Schnittstelle und nicht für die Implementierung" mit einer wichtigen Einschränkung: Nur C # Unterstützt Mehrfachvererbung mit Schnittstellenvererbung (und nicht mit Implementierungsvererbung).

Die architektonischen Gründe für Schnittstellen in C # sind die Empfehlungen von GoF.

Ich hoffe das kann helfen.

Herzliche Grüße, Gastón


1

Das Anwenden von Erweiterungsmethoden auf eine Schnittstelle ist nützlich, um ein allgemeines Verhalten auf Klassen anzuwenden, die möglicherweise nur eine gemeinsame Schnittstelle verwenden. Solche Klassen sind möglicherweise bereits vorhanden und können auf andere Weise für Erweiterungen geschlossen werden.

Für neue Designs würde ich die Verwendung von Erweiterungsmethoden für Schnittstellen bevorzugen. Bei einer Hierarchie von Typen bieten Erweiterungsmethoden eine Möglichkeit, das Verhalten zu erweitern, ohne die gesamte Hierarchie zur Änderung öffnen zu müssen.

Erweiterungsmethoden können jedoch nicht auf die private Implementierung zugreifen. Wenn ich also die private Implementierung an der Spitze einer Hierarchie hinter einer öffentlichen Schnittstelle einkapseln muss, ist eine abstrakte Klasse der einzige Weg.


Nein, das ist nicht der einzig sichere Weg. Es gibt einen anderen sichereren Weg. Das sind die Erweiterungsmethoden. Clients werden überhaupt nicht infiziert. Deshalb habe ich Schnittstellen + Erweiterungsmethoden erwähnt.
Gulshan

@ Ed James Ich denke, "weniger leistungsstark" = "flexibler" Wann würden Sie eine leistungsfähigere abstrakte Klasse anstelle einer flexibleren Schnittstelle wählen?
Gulshan

@Ed James In asp.mvc wurden von Anfang an Erweiterungsmethoden verwendet. Was denkst du darüber?
Gulshan

0

Ich denke, dass in C # Mehrfachvererbung nicht unterstützt wird und deshalb wird das Konzept der Schnittstelle in C # eingeführt.

Bei abstrakten Klassen wird die Mehrfachvererbung ebenfalls nicht unterstützt. So ist es einfach zu entscheiden, ob abstrakte Klassen oder Interfaces verwendet werden sollen.


Genauer gesagt handelt es sich um reine abstrakte Klassen, die in C # als "Interfaces" bezeichnet werden.
Johan Boulé

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.