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.
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?Antworten:
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:
Diese Basisklasse verfügt wahrscheinlich über Standardmethoden wie:
All dies sind Verhaltensweisen, die zwischen beiden Arten mehr oder weniger dieselbe Umsetzung haben. Um dies zu definieren, haben Sie:
Nehmen wir nun an, es gibt andere Säugetiere, die wir normalerweise in einem Zoo sehen werden:
Dies wird noch gültig sein , weil der Kern der Funktionalität
Feed()
undMate()
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:
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:
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 .
quelle
Nun, Josh Bloch sagte sich in Effective Java 2d :
Bevorzugen Sie Schnittstellen gegenüber abstrakten Klassen
Einige Hauptpunkte:
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.
quelle
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 "
Foo
ist verfügbar", daher dieIDisposable
Schnittstelle in C #.quelle
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.
quelle
Schnittstellen
Basisklassen
quelle
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.
quelle
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:
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.
quelle
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.
quelle
Hier ist die grundlegende und einfache Definition von Schnittstelle und Basisklasse:
Prost
quelle
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.
quelle
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
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.
quelle
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.
quelle
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.
quelle
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.
quelle
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:
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:
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!
quelle
Der Fall für Basisklassen über Schnittstellen wurde in den Submain .NET-Codierungsrichtlinien ausführlich erläutert:
quelle
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).
quelle
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.
quelle
Bevorzugen Sie Schnittstellen gegenüber abstrakten Klassen
Begründung, die wichtigsten zu berücksichtigenden Punkte [zwei hier bereits erwähnte] sind:
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 ...
quelle
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).
quelle
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.
quelle
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:
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.
quelle
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.
quelle
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 Sie
UserControl
. Aber jetzt können Sie nicht mehr erbenPetBase
. Dies zwingt Sie dazu, sich neu zu organisieren, umPetBase
stattdessen ein Klassenmitglied zu machen .quelle
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.
quelle
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.
quelle
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 !!!
quelle
Ich habe festgestellt, dass ein Muster von Schnittstelle> Zusammenfassung> Beton im folgenden Anwendungsfall funktioniert:
Die abstrakte Klasse definiert gemeinsame Standardattribute der konkreten Klassen, erzwingt jedoch die Schnittstelle. Zum Beispiel:
Da nun alle Säugetiere Haare und Brustwarzen haben (AFAIK, ich bin kein Zoologe), können wir dies in die abstrakte Basisklasse rollen
Und dann definieren die konkreten Klassen lediglich, dass sie aufrecht gehen.
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.
quelle
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.
quelle
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
quelle