Schnittstelle vs Basisklasse

767

Wann sollte ich eine Schnittstelle verwenden und wann sollte ich eine Basisklasse verwenden?

Sollte es immer eine Schnittstelle sein, wenn ich keine Basisimplementierung der Methoden definieren möchte?

Wenn ich eine Hunde- und Katzenklasse habe. Warum sollte ich IPet anstelle von PetBase implementieren wollen? Ich kann verstehen, Schnittstellen für ISheds oder IBarks (IMakesNoise?) Zu haben, da diese auf Haustierbasis platziert werden können, aber ich verstehe nicht, welche für ein generisches Haustier verwendet werden sollen.

Heuler
quelle
11
Nur ein Punkt, den Sie meiner Meinung nach berücksichtigen sollten: Schnittstellen können mehrere Grenzen haben, die Sie möglicherweise erst in sehr späten Phasen kennen. Mit .NET können Sie beispielsweise eine Schnittstellenmitgliedsvariable nicht serialisieren. Wenn Sie also eine Klasse Zoo und ein Elementvariablenarray von IAnimals haben, können Sie Zoo nicht serialisieren (und das bedeutet, dass WebServices oder andere Dinge geschrieben werden müssen, die eine Serialisierung erfordern ein Schmerz).
Synhershko
1
Diese Frage könnte helfen, das Konzept der Schnittstellen zu verstehen. stackoverflow.com/q/8531292/1055241
gprathour
Ich bin nur neugierig. Ich habe in der CLR über C # folgenden Auszug getroffen : 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 verstehen, was im Auszug gemeint ist. Wir können einige Basistypen erstellen und für jeden einen abgeleiteten Typ erstellen, sodass ein Entwickler einen Basistyp auswählen kann. Könnte jemand bitte erklären, was ich vermisse? Ich glaube, es kann ein Teil dieser Frage sein. Oder sollte ich einen anderen über den spezifischen Auszug posten?
qqqqqqq

Antworten:

501

Nehmen wir Ihr Beispiel für eine Hunde- und eine Katzenklasse und veranschaulichen Sie mit C #:

Sowohl ein Hund als auch eine Katze sind Tiere, insbesondere vierbeinige Säugetiere (Tiere sind viel zu allgemein). Nehmen wir an, Sie haben für beide eine abstrakte Klasse Säugetier:

public abstract class Mammal

Diese Basisklasse verfügt wahrscheinlich über Standardmethoden wie:

  • Futter
  • Kamerad

All dies sind Verhaltensweisen, die zwischen beiden Arten mehr oder weniger dieselbe Umsetzung haben. Um dies zu definieren, haben Sie:

public class Dog : Mammal
public class Cat : Mammal

Nehmen wir nun an, es gibt andere Säugetiere, die wir normalerweise in einem Zoo sehen werden:

public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

Dies wird noch gültig sein , weil der Kern der Funktionalität Feed()und Mate()wird immer noch die gleiche sein.

Giraffen, Nashörner und Flusspferde sind jedoch nicht gerade Tiere, aus denen man Haustiere machen kann. Hier ist eine Schnittstelle hilfreich:

public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

Die Umsetzung des oben genannten Vertrags ist zwischen Katze und Hund nicht gleich. Es wird eine schlechte Idee sein, ihre Implementierungen in eine abstrakte Klasse zu setzen, um sie zu erben.

Ihre Hunde- und Katzendefinitionen sollten nun folgendermaßen aussehen:

public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

Theoretisch können Sie sie von einer höheren Basisklasse aus überschreiben, aber im Wesentlichen können Sie über eine Schnittstelle nur die benötigten Dinge zu einer Klasse hinzufügen, ohne dass eine Vererbung erforderlich ist.

Da Sie normalerweise nur von einer abstrakten Klasse erben können (in den meisten statisch typisierten OO-Sprachen, dh ... Ausnahmen sind C ++), aber mehrere Schnittstellen implementieren können, können Sie Objekte streng nach Bedarf erstellen .

Jon Limjap
quelle
149
Ich denke nicht, dass es so einfach ist. Sie haben die Frage (Anforderungen) leicht geändert, damit die Benutzeroberfläche sinnvoller ist. Sie sollten sich immer fragen, ob Sie einen Vertrag (Schnittstelle) oder eine gemeinsam genutzte Implementierung (Basisklasse) definieren.
David Pokluda
18
Die Schnittstelle ist ein Vertrag. Sie legen nur den Teil des Vertrags offen, den ein Service erfordert. Wenn Sie einen 'PettingZoo' haben, möchten Sie dem Benutzer auf keinen Fall 'Mate'-ing aussetzen!
Anthony Mastrean
10
@ David Touche, obwohl ich es getan habe, um besser zu veranschaulichen, wofür eine Schnittstelle ist und was eine abstrakte Klasse für sein Verständnis ist. Ein Hund und eine Katze scheinen keine strenge Anforderung zu sein!
Jon Limjap
2
Es ist zu beachten, dass in interpretierten JIT-Umgebungen (insbesondere der JVM) virtuelle Methodenaufrufe je nach Kontext erheblich schneller sind als Schnittstellenmethodenaufrufe . Ich betone "Kontext", weil die JVM die langsamen Methodensuchen oft optimieren kann. (z. B. wenn eine Liste von Schnittstellenvererbern in der Regel Instanzen derselben Klasse sind. Dies macht das Benchmarking leider etwas schwierig.) Wenn Sie versuchen, etwas leistungsempfindliches zu optimieren, und nur dann, sollten Sie dies berücksichtigen.
Philip Guin
14
Eine Schnittstelle ermöglicht es auch, einen Haustierfelsen zu baden und Tricks beizubringen, aber das Füttern und Paaren würde nicht unterstützt, da dies für einen Stein lächerlich wäre.
Eclux
146

Nun, Josh Bloch sagte sich in Effective Java 2d :

Bevorzugen Sie Schnittstellen gegenüber abstrakten Klassen

Einige Hauptpunkte:

  • Bestehende Klassen können einfach nachgerüstet werden, um eine neue Schnittstelle zu implementieren . Sie müssen lediglich die erforderlichen Methoden hinzufügen, falls diese noch nicht vorhanden sind, und der Klassendeklaration eine Implementierungsklausel hinzufügen.

  • Schnittstellen sind ideal zum Definieren von Mixins . Ein Mixin ist ein Typ, den eine Klasse zusätzlich zu ihrem „Primärtyp“ implementieren kann, um zu deklarieren, dass er ein optionales Verhalten bietet. Comparable ist beispielsweise eine Mixin-Schnittstelle, mit der eine Klasse deklarieren kann, dass ihre Instanzen in Bezug auf andere miteinander vergleichbare Objekte geordnet sind.

  • Schnittstellen ermöglichen die Konstruktion nichthierarchischer Frameworks . Typhierarchien eignen sich hervorragend zum Organisieren einiger Dinge, andere Dinge fallen jedoch nicht sauber in eine starre Hierarchie.

  • Schnittstellen ermöglichen sichere, leistungsstarke Funktionserweiterungen über die Wrapper-Class-Sprache. Wenn Sie abstrakte Klassen verwenden, um Typen zu definieren, bleibt dem Programmierer, der Funktionen hinzufügen möchte, keine andere Wahl, als die Vererbung zu verwenden.

Darüber hinaus können Sie die Vorteile von Schnittstellen und abstrakten Klassen kombinieren, indem Sie eine abstrakte Skelettimplementierungsklasse bereitstellen, die zu jeder nichttrivialen Schnittstelle passt, die Sie exportieren.

Andererseits sind Schnittstellen sehr schwer zu entwickeln. Wenn Sie einer Schnittstelle eine Methode hinzufügen, werden alle Implementierungen unterbrochen.

PS.: Kaufen Sie das Buch. Es ist viel detaillierter.

Marcio Aguiar
quelle
71
Wann immer eine Änderung an einer Schnittstelle vorgenommen werden muss, besteht die ununterbrochene Möglichkeit darin, eine neue Schnittstelle zu erstellen, die von der alten erbt. Dadurch bleiben vorhandene Implementierungen erhalten und Sie können in der neuen Implementierung alles tun, was Sie möchten.
Scott Lawrence
5
Wir haben "Interface Segregation Principle". Dieses Prinzip lehrt uns, darauf zu achten, wie wir unsere Schnittstellen schreiben. Wenn wir unsere Schnittstellen schreiben, sollten wir darauf achten, nur Methoden hinzuzufügen, die vorhanden sein sollten. Wenn wir Methoden hinzufügen, die nicht vorhanden sein sollten, müssen die Klassen, die die Schnittstelle implementieren, diese Methoden ebenfalls implementieren. Wenn wir beispielsweise eine Schnittstelle namens Worker erstellen und eine Methode für die Mittagspause hinzufügen, müssen alle Worker diese implementieren. Was ist, wenn der Arbeiter ein Roboter ist? Als Schlussfolgerung werden Schnittstellen, die nicht spezifische Methoden enthalten, als verschmutzte oder fette Schnittstellen bezeichnet.
Eklavyaa
Seit Java 8 können Sie mit den Standardmethoden den Schnittstellen neue Funktionen hinzufügen und die Abwärtskompatibilität für vorhandene Klassen sicherstellen, die diese Schnittstelle implementieren. Standardmethoden werden standardmäßig aufgerufen, wenn sie in der implementierenden Klasse nicht überschrieben werden. Alle implementierenden Klassen können entweder Standardmethoden überschreiben oder sie direkt mit instance.defaultMethod ()
Arpit Rastogi vom
118

Schnittstellen und Basisklassen repräsentieren zwei verschiedene Formen von Beziehungen.

Vererbung (Basisklassen) repräsentiert eine "is-a" -Beziehung. ZB ist ein Hund oder eine Katze ein Haustier. Diese Beziehung repräsentiert immer den (Einzel-) Zweck der Klasse (in Verbindung mit dem "Prinzip der Einzelverantwortung" ).

Schnittstellen hingegen repräsentieren zusätzliche Merkmale einer Klasse. Ich würde es eine "ist" -Beziehung nennen, wie in " Fooist verfügbar", daher die IDisposableSchnittstelle in C #.

Thomas Danecker
quelle
10
Von allen Antworten gibt diese die beste Mischung aus Kürze ohne Verlust an Klarheit
Matt Wilko
Jemand hat mir einmal gesagt, ich soll eine Schnittstelle verwenden, wenn eine "has-a" -Beziehung besteht. Ich bin mir nicht sicher, ob das immer stimmt. Mein Laptop hat einen Bildschirm. Sollte der Laptop also IScreen implementieren oder über eine Screen-Eigenschaft verfügen? Letzteres erscheint mir natürlicher.
Berend
1
@berend lustig, dass Sie das schreiben, weil die Bildschirme über Schnittstellen implementiert werden - VGA, HDMI usw.
Konstantin
Nur weil es OOP ist, müssen wir unsere Vorstellungskraft erweitern, um realen Szenarien zu folgen. Es gilt nicht immer.
Crismogram
110

Moderner Stil ist die Definition von IPet und PetBase.

Der Vorteil der Schnittstelle besteht darin, dass anderer Code sie ohne jegliche Bindung an anderen ausführbaren Code verwenden kann. Völlig "sauber". Auch Schnittstellen können gemischt werden.

Basisklassen sind jedoch nützlich für einfache Implementierungen und allgemeine Dienstprogramme. Stellen Sie also auch eine abstrakte Basisklasse bereit, um Zeit und Code zu sparen.

Jason Cohen
quelle
Haben Sie Ihren Kuchen und essen Sie ihn auch!
Darren Kopp
3
Eine Schnittstelle definiert, wie andere Klassen Ihren Code verwenden können. Eine Basisklasse hilft Implementierern bei der Implementierung Ihrer Schnittstelle. Zwei verschiedene Dinge für zwei verschiedene Zwecke.
Bill Michell
27
Hier gibt es nichts "Modernes". Basisklasse und Schnittstelle mit derselben API sind einfach redundant. Sie können diesen Ansatz in einigen Fällen verwenden, aber Sie sollten nicht verallgemeinern!
Smentek
3
Der am meisten unterstützte Kommentar zur zweiten unterstützten Antwort stimmt tatsächlich nicht mit der Antwort selbst überein, was interessant ist. Ich muss sagen, ich sehe viele Beispiele für das Nebeneinander von Schnittstelle und Basisklasse. In diesem Sinne ist es der "moderne" Weg. Im MVVM-Muster gibt es beispielsweise eine ViewModelBase-Klasse, die INotifyPropertyChanged implementiert. Aber als mein Kollege mich fragte, warum ich die Basisklasse habe, anstatt die Schnittstelle in jedem Ansichtsmodell zu implementieren, weiß ich nicht, wie ich ihn überzeugen kann
tete
1
Richtig. Es ist keine Frage des einen oder anderen. Sie existieren, um zwei sehr unterschiedliche Probleme anzugehen. Schnittstellen sind Verträge, die eine implementierende Klasse adressieren muss. Sie haben in jüngster Zeit (manchmal fanatisch) Gefallen für IoC- und TDD-Aspekte gefunden. Abstrakte / Basisklassen dienen dazu, hierarchisch gemeinsame Logik und Eigenschaften zu gruppieren. Es reduziert doppelten Code, was wiederum die Wartbarkeit der Lösungen erhöht und die Fehleranfälligkeit verringert.
Kommen Sie
63

Schnittstellen

  • Definiert den Vertrag zwischen 2 Modulen. Kann keine Implementierung haben.
  • In den meisten Sprachen können Sie mehrere Schnittstellen implementieren
  • Das Ändern einer Schnittstelle ist eine grundlegende Änderung. Alle Implementierungen müssen neu kompiliert / geändert werden.
  • Alle Mitglieder sind öffentlich. Implementierungen müssen alle Mitglieder implementieren.
  • Schnittstellen helfen beim Entkoppeln. Sie können Mock-Frameworks verwenden, um alles hinter einer Schnittstelle zu verspotten
  • Schnittstellen weisen normalerweise auf eine Art Verhalten hin
  • Schnittstellenimplementierungen sind voneinander entkoppelt / isoliert

Basisklassen

  • Ermöglicht das Hinzufügen einer Standardimplementierung , die Sie durch Ableitung kostenlos erhalten
  • Mit Ausnahme von C ++ können Sie nur von einer Klasse ableiten. Selbst wenn es aus mehreren Klassen stammen könnte, ist es normalerweise eine schlechte Idee.
  • Das Ändern der Basisklasse ist relativ einfach. Ableitungen müssen nichts Besonderes tun
  • Basisklassen können geschützte und öffentliche Funktionen deklarieren, auf die durch Ableitungen zugegriffen werden kann
  • Abstrakte Basisklassen können nicht einfach wie Schnittstellen verspottet werden
  • Basisklassen geben normalerweise die Typhierarchie an (IS A)
  • Klassenableitungen können von einem bestimmten Basisverhalten abhängen (über fundierte Kenntnisse der übergeordneten Implementierung verfügen). Dinge können chaotisch sein, wenn Sie eine Änderung an der Basisimplementierung für einen Kerl vornehmen und die anderen brechen.
Gishu
quelle
Hinweis: In den Richtlinien für das Framework-Design wird empfohlen, Basisklassen (im Gegensatz zu Schnittstellen) zu verwenden, da diese besser versioniert werden. Das Hinzufügen einer neuen Methode zu einer abstrakten Basisklasse in vNext ist eine ununterbrochene Änderung.
Gishu
59

Im Allgemeinen sollten Sie Schnittstellen gegenüber abstrakten Klassen bevorzugen. Ein Grund für die Verwendung einer abstrakten Klasse besteht darin, dass Sie eine gemeinsame Implementierung unter konkreten Klassen haben. Natürlich sollten Sie weiterhin eine Schnittstelle (IPet) deklarieren und diese Schnittstelle von einer abstrakten Klasse (PetBase) implementieren lassen. Mithilfe kleiner, unterschiedlicher Schnittstellen können Sie mehrere verwenden, um die Flexibilität weiter zu verbessern. Schnittstellen ermöglichen ein Höchstmaß an Flexibilität und Portabilität von Typen über Grenzen hinweg. Übergeben Sie beim Übergeben von Referenzen über Grenzen hinweg immer die Schnittstelle und nicht den konkreten Typ. Dies ermöglicht es dem empfangenden Ende, die konkrete Implementierung zu bestimmen, und bietet maximale Flexibilität. Dies gilt absolut für die TDD / BDD-Programmierung.

Die Viererbande erklärte in ihrem Buch: "Da die Vererbung eine Unterklasse Details der Implementierung ihrer Eltern aussetzt, wird oft gesagt, dass die Vererbung die Kapselung unterbricht." Ich glaube das ist wahr.

Kilhoffer
quelle
Ja. Persönlich glaube ich, dass dies Arsch rückwärts ist. Schnittstellen sollten die Mindestfunktionalität für einen Typ enthalten, und Basisklassen sollten ein umfassendes Framework bereitstellen, auf dem Anpassungen basieren können. Wenn Sie das in eine Benutzeroberfläche einfügen, ist die Implementierung nur sehr schwierig.
Niemand hat gesagt, dass Ihre Schnittstellen riesig sein müssen. Kleinere, mehrfache Schnittstellen und umfangreichere Basisklassen bilden eine großartige API.
Kilhoffer
3
Ist es nur ich oder teilen die meisten "Common Worker" -Klassen eine gemeinsame Implementierung? In diesem Zusammenhang verstößt es gegen Ihre allgemeine Regel, Schnittstellen zu bevorzugen. Ich würde Ihre Verallgemeinerung in zwei Verallgemeinerungen umwandeln: Diese allgemeinen Klassen, die keine oder wenig Logik enthalten, sollten eine Schnittstelle implementieren. Die allgemeinen Klassen, die eine "anständige" Menge an Logik enthalten, sollten von einer Basisklasse abgeleitet sein (da sie höchstwahrscheinlich die Funktionalität gemeinsam nutzen).
goku_da_master
@Kilhoffer "Schnittstellen ermöglichen ein Höchstmaß an Flexibilität und Portabilität von Typen über Grenzen hinweg" Bitte erläutern Sie diese Aussage.
JAVA
49

Dies ist ziemlich .NET-spezifisch, aber das Buch Framework Design Guidelines argumentiert, dass Klassen im Allgemeinen mehr Flexibilität in einem sich entwickelnden Framework bieten. Sobald eine Schnittstelle ausgeliefert wurde, können Sie sie nicht mehr ändern, ohne den Code zu beschädigen, der diese Schnittstelle verwendet hat. Mit einer Klasse können Sie sie jedoch ändern und den Code, der mit ihr verknüpft ist, nicht beschädigen. Solange Sie die richtigen Änderungen vornehmen, einschließlich des Hinzufügens neuer Funktionen, können Sie Ihren Code erweitern und weiterentwickeln.

Krzysztof Cwalina sagt auf Seite 81:

Im Verlauf der drei Versionen von .NET Framework habe ich mit einigen Entwicklern in unserem Team über diese Richtlinie gesprochen. Viele von ihnen, einschließlich derer, die anfänglich mit den Richtlinien nicht einverstanden waren, haben erklärt, dass sie es bedauern, eine API als Schnittstelle ausgeliefert zu haben. Ich habe noch nicht einmal von einem Fall gehört, in dem jemand bedauert hat, dass er eine Klasse verschickt hat.

Davon abgesehen gibt es sicherlich einen Platz für Schnittstellen. Als allgemeine Richtlinie sollten Sie immer eine abstrakte Basisklassenimplementierung einer Schnittstelle bereitstellen, wenn nicht anders als ein Beispiel für eine Implementierung der Schnittstelle. Im besten Fall spart diese Basisklasse viel Arbeit.

Fryguybob
quelle
19

Juan,

Ich stelle mir Schnittstellen gerne als eine Möglichkeit vor, eine Klasse zu charakterisieren. Eine bestimmte Hunderassenklasse, beispielsweise ein YorkshireTerrier, stammt möglicherweise von der Elternhundeklasse ab, implementiert jedoch auch IFurry, IStubby und IYippieDog. Die Klasse definiert also, was die Klasse ist, aber die Schnittstelle sagt uns etwas darüber.

Dies hat den Vorteil, dass ich beispielsweise alle IYippieDogs sammeln und in meine Ocean-Sammlung werfen kann. Jetzt kann ich über eine bestimmte Gruppe von Objekten greifen und solche finden, die die Kriterien erfüllen, die ich betrachte, ohne die Klasse zu genau zu untersuchen.

Ich finde, dass Schnittstellen wirklich eine Teilmenge des öffentlichen Verhaltens einer Klasse definieren sollten. Wenn es das gesamte öffentliche Verhalten für alle implementierten Klassen definiert, muss es normalerweise nicht vorhanden sein. Sie sagen mir nichts Nützliches.

Dieser Gedanke widerspricht jedoch der Idee, dass jede Klasse eine Schnittstelle haben sollte und Sie Code für die Schnittstelle haben sollten. Das ist in Ordnung, aber am Ende gibt es viele Eins-zu-Eins-Schnittstellen zu Klassen, was die Dinge verwirrt. Ich verstehe, dass die Idee ist, dass es nicht wirklich etwas kostet, und jetzt können Sie Dinge mit Leichtigkeit ein- und austauschen. Ich finde jedoch, dass ich das selten mache. Die meiste Zeit ändere ich nur die vorhandene Klasse an Ort und Stelle und habe genau die gleichen Probleme, die ich immer hatte, wenn die öffentliche Schnittstelle dieser Klasse geändert werden muss, außer dass ich sie jetzt an zwei Stellen ändern muss.

Wenn Sie also wie ich denken, würden Sie definitiv sagen, dass Katze und Hund IPettable sind. Es ist eine Charakterisierung, die beiden entspricht.

Das andere ist jedoch, ob sie dieselbe Basisklasse haben sollten. Die Frage ist, ob sie allgemein als dasselbe behandelt werden müssen. Sicher sind sie beide Tiere, aber passt das dazu, wie wir sie zusammen verwenden werden?

Angenommen, ich möchte alle Tierklassen sammeln und in meinen Archenbehälter legen.

Oder müssen sie Säugetiere sein? Vielleicht brauchen wir eine Art Kreuztiermelkfabrik?

Müssen sie überhaupt miteinander verbunden werden? Reicht es aus, nur zu wissen, dass beide IPettable sind?

Ich habe oft den Wunsch, eine ganze Klassenhierarchie abzuleiten, wenn ich wirklich nur eine Klasse brauche. Ich mache es in Erwartung eines Tages, wenn ich es brauche, und normalerweise mache ich es nie. Selbst wenn ich das tue, muss ich normalerweise viel tun, um das Problem zu beheben. Das liegt daran, dass die erste Klasse, die ich erschaffe, nicht der Hund ist, ich bin nicht so glücklich, sondern der Schnabeltier. Jetzt basiert meine gesamte Klassenhierarchie auf dem bizarren Fall und ich habe viel verschwendeten Code.

Möglicherweise stellen Sie auch irgendwann fest, dass nicht alle Katzen IPettable sind (wie diese haarlose). Jetzt können Sie diese Schnittstelle in alle passenden Ableitungsklassen verschieben. Sie werden feststellen, dass eine viel weniger bahnbrechende Änderung, dass plötzlich Katzen nicht mehr von PettableBase abgeleitet sind.

Flory
quelle
18

Hier ist die grundlegende und einfache Definition von Schnittstelle und Basisklasse:

  • Basisklasse = Objektvererbung.
  • Schnittstelle = funktionale Vererbung.

Prost

RWendi
quelle
12

Ich empfehle, wann immer möglich Komposition statt Vererbung zu verwenden. Verwenden Sie Schnittstellen, aber Elementobjekte für die Basisimplementierung. Auf diese Weise können Sie eine Factory definieren, die Ihre Objekte so konstruiert, dass sie sich auf eine bestimmte Weise verhalten. Wenn Sie das Verhalten ändern möchten, erstellen Sie eine neue Factory-Methode (oder abstrakte Factory), mit der verschiedene Arten von Unterobjekten erstellt werden.

In einigen Fällen stellen Sie möglicherweise fest, dass Ihre primären Objekte überhaupt keine Schnittstellen benötigen, wenn das gesamte veränderbare Verhalten in Hilfsobjekten definiert ist.

Anstelle von IPet oder PetBase erhalten Sie möglicherweise ein Haustier mit einem IFurBehavior-Parameter. Der Parameter IFurBehavior wird von der CreateDog () -Methode der PetFactory festgelegt. Dieser Parameter wird für die Methodehed () aufgerufen.

Wenn Sie dies tun, werden Sie feststellen, dass Ihr Code viel flexibler ist und die meisten Ihrer einfachen Objekte mit sehr grundlegenden systemweiten Verhaltensweisen umgehen.

Ich empfehle dieses Muster auch in Sprachen mit mehreren Vererbungen.

Joe
quelle
12

Es wird in diesem Java World-Artikel gut erklärt .

Persönlich neige ich dazu, Schnittstellen zu verwenden, um Schnittstellen zu definieren - dh Teile des Systemdesigns, die angeben, wie auf etwas zugegriffen werden soll.

Es ist nicht ungewöhnlich, dass eine Klasse eine oder mehrere Schnittstellen implementiert.

Abstrakte Klassen verwende ich als Grundlage für etwas anderes.

Das Folgende ist ein Auszug aus dem oben erwähnten Artikel JavaWorld.com, Autor Tony Sintes, 20.04.01


Schnittstelle vs. abstrakte Klasse

Die Auswahl von Schnittstellen und abstrakten Klassen ist kein Entweder-Oder-Vorschlag. Wenn Sie Ihr Design ändern müssen, machen Sie es zu einer Schnittstelle. Möglicherweise verfügen Sie jedoch über abstrakte Klassen, die ein Standardverhalten bereitstellen. Abstrakte Klassen sind ausgezeichnete Kandidaten innerhalb von Anwendungsrahmen.

In abstrakten Klassen können Sie einige Verhaltensweisen definieren. Sie zwingen Ihre Unterklassen, andere bereitzustellen. Wenn Sie beispielsweise über ein Anwendungsframework verfügen, kann eine abstrakte Klasse Standarddienste wie die Ereignis- und Nachrichtenbehandlung bereitstellen. Mit diesen Diensten kann sich Ihre Anwendung in Ihr Anwendungsframework einbinden. Es gibt jedoch einige anwendungsspezifische Funktionen, die nur Ihre Anwendung ausführen kann. Zu diesen Funktionen können Start- und Herunterfahraufgaben gehören, die häufig anwendungsabhängig sind. Anstatt zu versuchen, dieses Verhalten selbst zu definieren, kann die abstrakte Basisklasse abstrakte Methoden zum Herunterfahren und Starten deklarieren. Die Basisklasse weiß, dass sie diese Methoden benötigt, aber eine abstrakte Klasse lässt Ihre Klasse zugeben, dass sie nicht weiß, wie diese Aktionen ausgeführt werden sollen. es weiß nur, dass es die Aktionen initiieren muss. Wenn es Zeit ist zu starten, Die abstrakte Klasse kann die Startmethode aufrufen. Wenn die Basisklasse diese Methode aufruft, ruft Java die von der untergeordneten Klasse definierte Methode auf.

Viele Entwickler vergessen, dass eine Klasse, die eine abstrakte Methode definiert, diese Methode auch aufrufen kann. Abstrakte Klassen sind eine hervorragende Möglichkeit, geplante Vererbungshierarchien zu erstellen. Sie sind auch eine gute Wahl für Nicht-Blatt-Klassen in Klassenhierarchien.

Klasse vs. Schnittstelle

Einige sagen, Sie sollten alle Klassen in Bezug auf Schnittstellen definieren, aber ich denke, die Empfehlung scheint etwas extrem zu sein. Ich verwende Schnittstellen, wenn ich sehe, dass sich etwas in meinem Design häufig ändert.

Mit dem Strategiemuster können Sie beispielsweise neue Algorithmen und Prozesse in Ihr Programm einbinden, ohne die Objekte zu ändern, die sie verwenden. Ein Media Player kann möglicherweise CDs, MP3s und WAV-Dateien abspielen. Natürlich möchten Sie diese Wiedergabealgorithmen nicht fest in den Player codieren. Dadurch wird es schwierig, ein neues Format wie AVI hinzuzufügen. Darüber hinaus wird Ihr Code mit nutzlosen case-Anweisungen übersät sein. Und um die Verletzung zusätzlich zu beleidigen, müssen Sie diese Fallaussagen jedes Mal aktualisieren, wenn Sie einen neuen Algorithmus hinzufügen. Alles in allem ist dies keine sehr objektorientierte Art zu programmieren.

Mit dem Strategiemuster können Sie einfach den Algorithmus hinter einem Objekt kapseln. In diesem Fall können Sie jederzeit neue Medien-Plug-Ins bereitstellen. Nennen wir die Plug-In-Klasse MediaStrategy. Dieses Objekt hätte eine Methode: playStream (Streams). Um einen neuen Algorithmus hinzuzufügen, erweitern wir einfach unsere Algorithmusklasse. Wenn das Programm nun auf den neuen Medientyp trifft, delegiert es einfach das Abspielen des Streams an unsere Medienstrategie. Natürlich benötigen Sie einige Installationsarbeiten, um die benötigten Algorithmusstrategien ordnungsgemäß zu instanziieren.

Dies ist ein ausgezeichneter Ort, um eine Schnittstelle zu verwenden. Wir haben das Strategiemuster verwendet, das deutlich auf eine Stelle im Design hinweist, die sich ändern wird. Daher sollten Sie die Strategie als Schnittstelle definieren. Im Allgemeinen sollten Sie Schnittstellen der Vererbung vorziehen, wenn ein Objekt einen bestimmten Typ haben soll. in diesem Fall MediaStrategy. Es ist gefährlich, sich bei der Typidentität auf die Vererbung zu verlassen. Es bindet Sie an eine bestimmte Vererbungshierarchie. Java erlaubt keine Mehrfachvererbung, daher können Sie nichts erweitern, was Ihnen eine nützliche Implementierung oder mehr Typidentität verleiht.

Richard Harrison
quelle
2
+1. "Es ist gefährlich, sich bei der Typidentität auf die Vererbung zu verlassen. Sie ist an eine bestimmte Vererbungshierarchie gebunden." Dieser Satz beschreibt perfekt meine Gründe für die Bevorzugung von Schnittstellen.
Ingenieur
Erstellen Sie darüber hinaus die Implementierung hinter jeder Methode Ihrer Schnittstelle, anstatt sie zu erweitern.
Ingenieur
10

Denken Sie auch daran, nicht in OO mitgerissen zu werden ( siehe Blog ) und Objekte immer basierend auf dem erforderlichen Verhalten zu modellieren. Wenn Sie eine App entwerfen, bei der das einzige Verhalten, das Sie benötigen, ein generischer Name und eine Art für ein Tier ist, benötigen Sie nur Ein Klassentier mit einer Eigenschaft für den Namen anstelle von Millionen von Klassen für jedes mögliche Tier auf der Welt.

Sijin
quelle
10

Ich habe eine grobe Faustregel

Funktionalität: wahrscheinlich in allen Teilen unterschiedlich: Schnittstelle.

Daten und Funktionalitätsteile sind größtenteils gleich, Teile unterschiedlich: abstrakte Klasse.

Daten und Funktionen funktionieren tatsächlich, wenn sie nur mit geringfügigen Änderungen erweitert werden: normale (konkrete) Klasse

Daten und Funktionalität, keine Änderungen geplant: normale (konkrete) Klasse mit endgültigem Modifikator.

Daten und möglicherweise Funktionalität: schreibgeschützt: Mitglieder auflisten.

Dies ist sehr grob und fertig und überhaupt nicht genau definiert, aber es gibt ein Spektrum von Schnittstellen, bei denen alles geändert werden soll, in Aufzählungen, bei denen alles ein bisschen wie eine schreibgeschützte Datei festgelegt ist.

Akhil Jain
quelle
7

Schnittstellen sollten klein sein. Wirklich klein. Wenn Sie Ihre Objekte wirklich zerlegen, enthalten Ihre Schnittstellen wahrscheinlich nur einige sehr spezifische Methoden und Eigenschaften.

Abstrakte Klassen sind Abkürzungen. Gibt es Dinge, die alle Derivate von PetBase gemeinsam haben und die Sie einmal codieren und erledigen können? Wenn ja, dann ist es Zeit für eine abstrakte Klasse.

Abstrakte Klassen sind ebenfalls einschränkend. Während sie Ihnen eine gute Verknüpfung zum Erstellen von untergeordneten Objekten bieten, kann jedes Objekt nur eine abstrakte Klasse implementieren. Oft finde ich dies eine Einschränkung der abstrakten Klassen, und deshalb verwende ich viele Schnittstellen.

Abstrakte Klassen können mehrere Schnittstellen enthalten. Ihre abstrakte PetBase-Klasse implementiert möglicherweise IPet (Haustiere haben Besitzer) und IDigestion (Haustiere fressen oder sollten es zumindest). PetBase wird IMammal jedoch wahrscheinlich nicht implementieren, da nicht alle Haustiere Säugetiere und nicht alle Säugetiere Haustiere sind. Sie können eine MammalPetBase hinzufügen, die PetBase erweitert, und IMammal hinzufügen. FishBase könnte PetBase haben und IFish hinzufügen. IFish hätte ISwim und IUnderwaterBreather als Schnittstellen.

Ja, mein Beispiel ist für das einfache Beispiel stark überkompliziert, aber das ist ein Teil der großartigen Sache, wie Schnittstellen und abstrakte Klassen zusammenarbeiten.

Jarrett Meyer
quelle
7

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!

Jason Roell
quelle
6

Der Fall für Basisklassen über Schnittstellen wurde in den Submain .NET-Codierungsrichtlinien ausführlich erläutert:

Basisklassen vs. Schnittstellen Ein Schnittstellentyp ist eine Teilbeschreibung eines Werts, der möglicherweise von vielen Objekttypen unterstützt wird. Verwenden Sie nach Möglichkeit Basisklassen anstelle von Schnittstellen. Aus Sicht der Versionierung sind Klassen flexibler als Schnittstellen. Mit einer Klasse können Sie Version 1.0 ausliefern und dann in Version 2.0 der Klasse eine neue Methode hinzufügen. Solange die Methode nicht abstrakt ist, funktionieren vorhandene abgeleitete Klassen unverändert weiter.

Da Schnittstellen keine Vererbung der Implementierung unterstützen, gilt das für Klassen geltende Muster nicht für Schnittstellen. Das Hinzufügen einer Methode zu einer Schnittstelle entspricht dem Hinzufügen einer abstrakten Methode zu einer Basisklasse. Jede Klasse, die die Schnittstelle implementiert, wird unterbrochen, da die Klasse die neue Methode nicht implementiert. Schnittstellen sind in folgenden Situationen geeignet:

  1. Mehrere nicht verwandte Klassen möchten das Protokoll unterstützen.
  2. Diese Klassen haben bereits Basisklassen eingerichtet (einige sind beispielsweise Benutzeroberflächensteuerelemente (UI) und andere XML-Webdienste).
  3. Aggregation ist nicht angemessen oder praktikabel. In allen anderen Situationen ist die Klassenvererbung ein besseres Modell.
YeahStu
quelle
Ich denke, diese Antwort sollte mehr Aufmerksamkeit erhalten. Es widerspricht hier vielen Antworten. Ich würde nicht sagen, dass ich völlig einverstanden bin, aber hier gibt es großartige Punkte.
KayleeFrye_onDeck
5

Ein wichtiger Unterschied besteht darin, dass Sie nur eine Basisklasse erben können , aber viele Schnittstellen implementieren können. Sie möchten eine Basisklasse also nur verwenden, wenn Sie absolut sicher sind, dass Sie nicht auch eine andere Basisklasse erben müssen. Wenn Sie feststellen, dass Ihre Benutzeroberfläche immer größer wird, sollten Sie versuchen, sie in einige logische Teile aufzuteilen, die unabhängige Funktionen definieren, da es keine Regel gibt, dass Ihre Klasse nicht alle implementieren kann (oder dass Sie eine andere definieren können Schnittstelle, die sie alle erbt, um sie zu gruppieren).

Joel Coehoorn
quelle
4

Als ich anfing, etwas über objektorientierte Programmierung zu lernen, machte ich den einfachen und wahrscheinlich häufigen Fehler, Vererbung zu verwenden, um gemeinsames Verhalten zu teilen - selbst wenn dieses Verhalten für die Natur des Objekts nicht wesentlich war.

Um weiter auf einem Beispiel aufzubauen, das in dieser speziellen Frage häufig verwendet wird, gibt es viele Dinge, die petbar sind - Freundinnen, Autos, flauschige Decken ... -, also hätte ich vielleicht eine Petable-Klasse gehabt, die dieses gemeinsame Verhalten lieferte, und verschiedene Klassen, die erbten davon.

Petabel zu sein, gehört jedoch nicht zur Natur eines dieser Objekte. Es gibt weitaus wichtigere Konzepte, die für ihre Natur wesentlich sind - die Freundin ist eine Person, das Auto ist ein Landfahrzeug, die Katze ist ein Säugetier ...

Verhaltensweisen sollten zuerst Schnittstellen (einschließlich der Standardschnittstelle der Klasse) zugewiesen und nur dann zu einer Basisklasse heraufgestuft werden, wenn sie (a) einer großen Gruppe von Klassen gemeinsam sind, die Teilmengen einer größeren Klasse sind - im gleichen Sinne wie "Katze" und "Person" sind Untergruppen von "Säugetier".

Der Haken dabei ist, dass Sie, nachdem Sie das objektorientierte Design ausreichend besser verstanden haben als ich, dies normalerweise automatisch tun, ohne darüber nachzudenken. Die bloße Wahrheit der Aussage "Code für eine Schnittstelle, keine abstrakte Klasse" wird so offensichtlich, dass es Ihnen schwer fällt zu glauben, dass sich jemand die Mühe machen würde, es zu sagen - und zu versuchen, andere Bedeutungen darin einzulesen.

Eine andere Sache, die ich hinzufügen möchte, ist, dass wenn eine Klasse rein abstrakt ist - ohne nicht abstrakte, nicht vererbte Mitglieder oder Methoden, die einem Kind, Elternteil oder Client ausgesetzt sind - warum ist es dann eine Klasse? Es könnte in einigen Fällen durch eine Schnittstelle und in anderen Fällen durch Null ersetzt werden.

Don Edwards
quelle
Eine rein abstrakte Klasse kann Standardverhalten für Methoden bereitstellen. Dies ist nützlich, wenn Ihre konkreten Klassen alle gemeinsame Methoden verwenden, deren erneute Implementierung überflüssig wäre.
Adam Hughes
4

Bevorzugen Sie Schnittstellen gegenüber abstrakten Klassen

Begründung, die wichtigsten zu berücksichtigenden Punkte [zwei hier bereits erwähnte] sind:

  • Schnittstellen sind flexibler, da eine Klasse mehrere Schnittstellen implementieren kann. Da Java keine Mehrfachvererbung hat, verhindert die Verwendung abstrakter Klassen, dass Ihre Benutzer eine andere Klassenhierarchie verwenden. Bevorzugen Sie im Allgemeinen Schnittstellen, wenn keine Standardimplementierungen oder -status vorhanden sind. Java-Sammlungen bieten hierfür gute Beispiele (Map, Set usw.).
  • Abstrakte Klassen haben den Vorteil einer besseren Vorwärtskompatibilität. Sobald Clients eine Schnittstelle verwenden, können Sie sie nicht mehr ändern. Wenn sie eine abstrakte Klasse verwenden, können Sie dennoch Verhalten hinzufügen, ohne vorhandenen Code zu beschädigen. Wenn Kompatibilität ein Problem darstellt, sollten Sie abstrakte Klassen verwenden.
  • Selbst wenn Sie Standardimplementierungen oder einen internen Status haben, sollten Sie eine Schnittstelle und eine abstrakte Implementierung anbieten . Dies wird den Kunden helfen, ihnen aber auf Wunsch immer noch mehr Freiheit gewähren [1].
    Natürlich wurde das Thema an anderer Stelle ausführlich diskutiert [2,3].

[1] Es fügt natürlich mehr Code hinzu, aber wenn Kürze Ihr Hauptanliegen ist, sollten Sie Java wahrscheinlich an erster Stelle vermeiden!

[2] Joshua Bloch, Effektives Java, Punkte 16-18.

[3] http://www.codeproject.com/KB/ar ...

Jaimin Patel
quelle
3

Frühere Kommentare zur Verwendung abstrakter Klassen für die allgemeine Implementierung sind definitiv zutreffend. Ein Vorteil, den ich noch nicht erwähnt habe, ist, dass die Verwendung von Schnittstellen die Implementierung von Scheinobjekten für Unit-Tests erheblich vereinfacht. Wenn Sie IPet und PetBase so definieren, wie Jason Cohen es beschrieben hat, können Sie verschiedene Datenbedingungen ohne den Aufwand einer physischen Datenbank leicht verspotten (bis Sie entscheiden, dass es Zeit ist, die reale Sache zu testen).

Scott Lawrence
quelle
3

Verwenden Sie keine Basisklasse, es sei denn, Sie wissen, was dies bedeutet und dass es in diesem Fall gilt. Wenn dies zutrifft, verwenden Sie es, andernfalls verwenden Sie Schnittstellen. Beachten Sie jedoch die Antwort zu kleinen Schnittstellen.

Public Inheritance wird in OOD überbeansprucht und drückt viel mehr aus, als die meisten Entwickler erkennen oder bereit sind, dem gerecht zu werden. Siehe das Liskov-Substitutabilitätsprinzip

Kurz gesagt, wenn A "ein" B ist, dann benötigt A nicht mehr als B und liefert nicht weniger als B für jede Methode, die es aussetzt.

theschmitzer
quelle
3

Eine andere Option, die Sie berücksichtigen sollten, ist die Verwendung der "has-a" -Beziehung, auch bekannt als "wird in Bezug auf" oder "Komposition" implementiert. Manchmal ist dies eine sauberere und flexiblere Art, Dinge zu strukturieren, als die Verwendung von "is-a" -Vererbung.

Es mag logischerweise nicht so sinnvoll sein zu sagen, dass sowohl Hund als auch Katze ein Haustier "haben", aber es vermeidet häufige Fallstricke bei Mehrfachvererbung:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

Ja, dieses Beispiel zeigt, dass es viel Code-Duplizierung und mangelnde Eleganz gibt, wenn man Dinge auf diese Weise macht. Man sollte sich aber auch darüber im Klaren sein, dass dies dazu beiträgt, Hund und Katze von der Haustierklasse zu entkoppeln (da Hund und Katze keinen Zugang zu den privaten Mitgliedern von Pet haben) und Hund und Katze Raum haben, von etwas anderem zu erben. - Möglicherweise die Säugetierklasse.

Die Zusammensetzung ist vorzuziehen, wenn kein privater Zugriff erforderlich ist und Sie sich nicht mit generischen Haustierreferenzen / -zeigern auf Hund und Katze beziehen müssen. Schnittstellen bieten Ihnen diese generische Referenzfunktion und können dazu beitragen, die Ausführlichkeit Ihres Codes zu verringern. Sie können jedoch auch Dinge verschleiern, wenn sie schlecht organisiert sind. Vererbung ist nützlich, wenn Sie Zugang für private Mitglieder benötigen. Wenn Sie diese verwenden, verpflichten Sie sich, Ihre Hunde- und Katzenklassen in hohem Maße an Ihre Haustierklasse zu koppeln, was hohe Kosten verursacht.

Zwischen Vererbung, Komposition und Schnittstellen gibt es keinen Weg, der immer richtig ist, und es ist hilfreich zu überlegen, wie alle drei Optionen harmonisch verwendet werden können. Von den dreien ist die Vererbung normalerweise die Option, die am seltensten verwendet werden sollte.

Parappa
quelle
3

Konzeptionell wird eine Schnittstelle verwendet, um eine Reihe von Methoden, die ein Objekt bereitstellt, formal und semi-formal zu definieren. Formal bedeutet eine Reihe von Methodennamen und -signaturen und semi-formal bedeutet von Menschen lesbare Dokumentation, die mit diesen Methoden verbunden ist.

Schnittstellen sind nur Beschreibungen einer API (schließlich steht API für Application Programming Interface ), sie können keine Implementierung enthalten und es ist nicht möglich, eine Schnittstelle zu verwenden oder auszuführen. Sie legen nur den Vertrag fest, wie Sie mit einem Objekt interagieren sollen.

Klassen stellen eine Implementierung bereit und können deklarieren, dass sie keine, eine oder mehrere Schnittstellen implementieren. Wenn eine Klasse geerbt werden soll, wird dem Klassennamen "Base" vorangestellt.

Es wird zwischen einer Basisklasse und einer abstrakten Basisklasse (ABC) unterschieden. ABCs mischen Schnittstelle und Implementierung miteinander. Abstrakt außerhalb der Computerprogrammierung bedeutet "Zusammenfassung", dh "Zusammenfassung == Schnittstelle". Eine abstrakte Basisklasse kann dann sowohl eine Schnittstelle als auch eine leere, teilweise oder vollständige Implementierung beschreiben, die vererbt werden soll.

Die Meinungen darüber, wann Schnittstellen im Vergleich zu abstrakten Basisklassen im Vergleich zu nur Klassen verwendet werden sollen , variieren stark, je nachdem, was Sie entwickeln und in welcher Sprache Sie entwickeln. Schnittstellen werden häufig nur statisch typisierten Sprachen wie Java oder C # zugeordnet Dynamisch typisierte Sprachen können auch Schnittstellen und abstrakte Basisklassen haben . In Python wird beispielsweise die Unterscheidung zwischen einer Klasse, die deklariert, dass sie eine Schnittstelle implementiert , und einem Objekt, das eine Instanz einer Klasse ist und bereitgestellt werden soll , deutlich gemacht diese Schnittstelle , deutlich gemacht. In einer dynamischen Sprache ist es möglich, dass zwei Objekte, die beide Instanzen derselben Klasse sind , deklarieren können, dass sie völlig unterschiedliche Schnittstellen bereitstellen. In Python ist dies nur für Objektattribute möglich, während Methoden den Status aller Objekte einer Klasse gemeinsam nutzen . In Ruby können Objekte jedoch Instanzmethoden haben, sodass die Schnittstelle zwischen zwei Objekten derselben Klasse möglich ist so stark variieren kann, wie es der Programmierer wünscht (Ruby hat jedoch keine explizite Möglichkeit, Schnittstellen zu deklarieren).

In dynamischen Sprachen wird die Schnittstelle zu einem Objekt häufig implizit angenommen, indem entweder ein Objekt überprüft und gefragt wird, welche Methoden es bietet ( schauen Sie, bevor Sie springen ), oder vorzugsweise indem Sie einfach versuchen, die gewünschte Schnittstelle für ein Objekt zu verwenden und Ausnahmen abzufangen, wenn das Objekt bietet diese Schnittstelle nicht an ( einfacher um Vergebung als um Erlaubnis zu bitten ). Dies kann zu "False Positives" führen, bei denen zwei Schnittstellen denselben Methodennamen haben, sich jedoch semantisch unterscheiden. Der Nachteil ist jedoch, dass Ihr Code flexibler ist, da Sie nicht im Voraus zu viel angeben müssen, um alle möglichen Verwendungen Ihres Codes zu antizipieren.

Weizen
quelle
2

Das hängt von Ihren Anforderungen ab. Wenn IPet einfach genug ist, würde ich das lieber implementieren. Andernfalls, wenn PetBase eine Menge Funktionen implementiert, die Sie nicht duplizieren möchten, dann haben Sie es.

Der Nachteil bei der Implementierung einer Basisklasse ist die Anforderung an override(odernew ) vorhandene Methoden. Dies macht sie zu virtuellen Methoden, was bedeutet, dass Sie vorsichtig sein müssen, wie Sie die Objektinstanz verwenden.

Schließlich bringt mich die einzige Vererbung von .NET um. Ein naives Beispiel: Angenommen, Sie erstellen ein Benutzersteuerelement, also erben SieUserControl . Aber jetzt können Sie nicht mehr erben PetBase. Dies zwingt Sie dazu, sich neu zu organisieren, um PetBasestattdessen ein Klassenmitglied zu machen .

Spoulson
quelle
2

Normalerweise implementiere ich auch nicht, bis ich eine brauche. Ich bevorzuge Schnittstellen gegenüber abstrakten Klassen, da dies etwas mehr Flexibilität bietet. Wenn es in einigen ererbenden Klassen ein allgemeines Verhalten gibt, verschiebe ich das nach oben und erstelle eine abstrakte Basisklasse. Ich sehe keine Notwendigkeit für beide, da sie im Wesentlichen den gleichen Zweck erfüllen und beide einen schlechten Code-Geruch (imho) haben, dass die Lösung überarbeitet wurde.

Bill die Eidechse
quelle
2

In Bezug auf C # können Schnittstellen und abstrakte Klassen in gewisser Hinsicht austauschbar sein. Die Unterschiede sind jedoch: i) Schnittstellen können keinen Code implementieren; ii) aus diesem Grund können Schnittstellen den Stapel nicht weiter zur Unterklasse aufrufen; und iii) nur eine abstrakte Klasse kann an eine Klasse vererbt werden, während mehrere Schnittstellen an einer Klasse implementiert werden können.

CarneyCode
quelle
2

Standardmäßig bietet die Schnittstelle eine Ebene für die Kommunikation mit anderem Code. Alle öffentlichen Eigenschaften und Methoden einer Klasse implementieren standardmäßig eine implizite Schnittstelle. Wir können eine Schnittstelle auch als eine Rolle definieren. Wenn eine Klasse diese Rolle spielen muss, muss sie sie implementieren und je nach der Klasse, die sie implementiert, unterschiedliche Implementierungsformen haben. Wenn Sie also über Schnittstelle sprechen, sprechen Sie über Polymorphismus, und wenn Sie über Basisklasse sprechen, sprechen Sie über Vererbung. Zwei Konzepte von oops !!!

Ganesh Kodiganti
quelle
2

Ich habe festgestellt, dass ein Muster von Schnittstelle> Zusammenfassung> Beton im folgenden Anwendungsfall funktioniert:

1.  You have a general interface (eg IPet)
2.  You have a implementation that is less general (eg Mammal)
3.  You have many concrete members (eg Cat, Dog, Ape)

Die abstrakte Klasse definiert gemeinsame Standardattribute der konkreten Klassen, erzwingt jedoch die Schnittstelle. Zum Beispiel:

public interface IPet{

    public boolean hasHair();

    public boolean walksUprights();

    public boolean hasNipples();
}

Da nun alle Säugetiere Haare und Brustwarzen haben (AFAIK, ich bin kein Zoologe), können wir dies in die abstrakte Basisklasse rollen

public abstract class Mammal() implements IPet{

     @override
     public walksUpright(){
         throw new NotSupportedException("Walks Upright not implemented");
     }

     @override
     public hasNipples(){return true}

     @override
     public hasHair(){return true}

Und dann definieren die konkreten Klassen lediglich, dass sie aufrecht gehen.

public class Ape extends Mammal(){

    @override
    public walksUpright(return true)
}

public class Catextends Mammal(){

    @override
    public walksUpright(return false)
}

Dieses Design ist schön, wenn es viele konkrete Klassen gibt und Sie nicht die Boilerplate pflegen möchten, nur um auf eine Schnittstelle zu programmieren. Wenn der Schnittstelle neue Methoden hinzugefügt würden, würden alle resultierenden Klassen zerstört, sodass Sie immer noch die Vorteile des Schnittstellenansatzes erhalten.

In diesem Fall könnte die Zusammenfassung genauso gut konkret sein; Die abstrakte Bezeichnung unterstreicht jedoch, dass dieses Muster verwendet wird.

Adam Hughes
quelle
1

Ein Erbe einer Basisklasse sollte eine "ist eine" Beziehung haben. Schnittstelle repräsentiert eine "implementiert eine" Beziehung. Verwenden Sie eine Basisklasse also nur, wenn Ihre Erben die Beziehung beibehalten.

Aaron Fischer
quelle
1

Verwenden Sie Schnittstellen, um einen Vertrag über Familien nicht verwandter Klassen durchzusetzen. Beispielsweise verfügen Sie möglicherweise über allgemeine Zugriffsmethoden für Klassen, die Sammlungen darstellen, jedoch radikal unterschiedliche Daten enthalten, dh eine Klasse repräsentiert möglicherweise eine Ergebnismenge aus einer Abfrage, während die andere die Bilder in einer Galerie darstellt. Sie können auch mehrere Schnittstellen implementieren, um die Funktionen der Klasse zu kombinieren (und zu kennzeichnen).

Verwenden Sie Vererbung, wenn die Klassen eine gemeinsame Beziehung haben und daher eine ähnliche strukturelle und verhaltensbezogene Signatur aufweisen, dh Auto, Motorrad, LKW und SUV sind alle Arten von Straßenfahrzeugen, die eine Anzahl von Rädern und eine Höchstgeschwindigkeit enthalten können

Sunwukung
quelle