Ich habe viele abstrakte Klassen / Methoden erstellt. Dann habe ich angefangen, Schnittstellen zu verwenden.
Jetzt bin ich mir nicht sicher, ob Interfaces abstrakte Klassen nicht überflüssig machen.
Sie benötigen eine vollständig abstrakte Klasse? Erstellen Sie stattdessen eine Schnittstelle. Sie benötigen eine abstrakte Klasse mit einer Implementierung? Erstellen Sie eine Schnittstelle, erstellen Sie eine Klasse. Erben Sie die Klasse, implementieren Sie die Schnittstelle. Ein zusätzlicher Vorteil besteht darin, dass einige Klassen die übergeordnete Klasse möglicherweise nicht benötigen, sondern lediglich die Schnittstelle implementieren.
Sind abstrakte Klassen / Methoden überholt?
object-oriented
architecture
inheritance
interfaces
Boris Yankov
quelle
quelle
Antworten:
Nein.
Schnittstellen können keine Standardimplementierung bereitstellen, abstrakte Klassen und Methoden. Dies ist besonders nützlich, um in vielen Fällen eine Codeduplizierung zu vermeiden.
Dies ist auch eine sehr gute Möglichkeit, die sequentielle Kopplung zu reduzieren. Ohne abstrakte Methode / Klassen können Sie kein Vorlagenmethodenmuster implementieren. Ich schlage vor, Sie schauen sich diesen Wikipedia-Artikel an: http://en.wikipedia.org/wiki/Template_method_pattern
quelle
Es gibt eine abstrakte Methode, mit der Sie sie aus Ihrer Basisklasse aufrufen, aber in einer abgeleiteten Klasse implementieren können. Ihre Basisklasse weiß also:
Dies ist eine einigermaßen gute Methode, um ein Loch im mittleren Muster zu implementieren, ohne dass die abgeleitete Klasse etwas über die Setup- und Bereinigungsaktivitäten weiß. Ohne abstrakte Methoden müsste man sich darauf verlassen, dass die abgeleitete Klasse implementiert
DoTask
und sich daran erinnert, immerbase.DoSetup()
und immer aufzurufenbase.DoCleanup()
.Bearbeiten
Vielen Dank auch an deadalnix für den Link zum Template Method Pattern , den ich oben beschrieben habe, ohne den Namen zu kennen. :)
quelle
public void DoTask(Action doWork)
Fruit
und Ihre abgeleitete Klasse istApple
, möchten Sie anrufenmyApple.Eat()
, nichtmyApple.Eat((a) => howToEatApple(a))
. Außerdem möchten Sie nichtApple
anrufen müssenbase.Eat(() => this.howToEatMe())
. Ich denke, es ist sauberer, nur eine abstrakte Methode zu überschreiben.Nein, sie sind nicht überholt.
Tatsächlich gibt es einen obskuren, aber grundlegenden Unterschied zwischen abstrakten Klassen / Methoden und Schnittstellen.
Wenn die Menge der Klassen, in denen eine dieser Klassen verwendet werden muss, ein gemeinsames Verhalten aufweist (verwandte Klassen, meine ich), dann entscheiden Sie sich für abstrakte Klassen / Methoden.
Beispiel: clerk, Officer, Director - alle diese Klassen haben CalculateSalary () gemeinsam, verwenden abstrakte Basisklassen. CalculateSalary () kann unterschiedlich implementiert werden, aber es gibt bestimmte andere Dinge wie GetAttendance (), die in der Basisklasse eine gemeinsame Definition haben.
Wenn Ihre Klassen nichts gemeinsam haben (im gewählten Kontext nicht verwandte Klassen), aber eine Aktion haben, die sich in der Implementierung stark unterscheidet, dann entscheiden Sie sich für Interface.
Beispiel: Kuh-, Bank-, Auto-, Telesope-unabhängige Klassen, aber Isortable kann vorhanden sein, um sie in einem Array zu sortieren.
Dieser Unterschied wird normalerweise ignoriert, wenn man sich aus einer polymorphen Perspektive nähert. Ich persönlich habe jedoch das Gefühl, dass es Situationen gibt, in denen einer aus dem oben erläuterten Grund besser geeignet ist als der andere.
quelle
Neben den anderen guten Antworten gibt es einen fundamentalen Unterschied zwischen Interfaces und abstrakten Klassen, den niemand speziell erwähnt hat, nämlich dass Interfaces weitaus weniger zuverlässig sind und daher eine viel größere Testlast auferlegen als abstrakte Klassen. Betrachten Sie beispielsweise diesen C # -Code:
Mir wird garantiert, dass es nur zwei Codepfade gibt, die ich testen muss. Der Autor von Frobbit kann sich darauf verlassen, dass der Frobber entweder rot oder grün ist.
Wenn wir stattdessen sagen:
Ich weiß jetzt absolut nichts über die Auswirkungen dieses Anrufs bei Frob. Ich muss sicher sein, dass der gesamte Code in Frobbit gegenüber jeder möglichen Implementierung von IFrobber robust ist , auch gegenüber Implementierungen von Personen, die inkompetent (schlecht) oder aktiv gegen mich oder meine Benutzer (weitaus schlimmer) sind.
Mit abstrakten Klassen können Sie all diese Probleme vermeiden. benutze sie!
quelle
Du sagst es selbst:
das klingt nach viel arbeit im vergleich zu 'die abstrakte klasse erben'. Sie können sich die Arbeit selbst machen, indem Sie sich aus puristischer Sicht dem Code nähern, aber ich finde, dass ich bereits genug zu tun habe, ohne zu versuchen, meinen Arbeitsaufwand ohne praktischen Nutzen zu erhöhen.
quelle
Wie ich in @deadnix-Post kommentiert habe: Teilweise Implementierungen sind ein Anti-Pattern, obwohl das Template-Pattern sie formalisiert.
Eine saubere Lösung für dieses Wikipedia-Beispiel des Vorlagenmusters :
Diese Lösung ist besser, da anstelle der Vererbung die Komposition verwendet wird . Das Vorlagenmuster führt eine Abhängigkeit zwischen der Implementierung der Monopoly-Regeln und der Implementierung der Art und Weise ein, wie Spiele ausgeführt werden sollen. Dies sind jedoch zwei völlig unterschiedliche Zuständigkeiten, und es gibt keinen guten Grund, sie zu koppeln.
quelle
Nein. Auch Ihre vorgeschlagene Alternative beinhaltet die Verwendung abstrakter Klassen. Da Sie die Sprache nicht angegeben haben, gehe ich gleich vor und sage, dass generischer Code ohnehin die bessere Option als spröde Vererbung ist. Abstrakte Klassen haben erhebliche Vorteile gegenüber Schnittstellen.
quelle
Abstrakte Klassen sind keine Schnittstellen. Das sind Klassen, die nicht instanziiert werden können.
Aber dann hätten Sie eine nicht abstrakte nutzlose Klasse. Abstrakte Methoden sind erforderlich, um die Funktionslücke in der Basisklasse auszufüllen.
Zum Beispiel mit dieser Klasse
Wie würden Sie diese Basisfunktionalität in einer Klasse implementieren, die weder
Frob()
noch hatIsFrobbingNeeded
?quelle
Ich bin ein Entwickler von Servlet-Frameworks, bei denen abstrakte Klassen eine wesentliche Rolle spielen. Ich würde mehr sagen, ich brauche semi-abstrakte Methoden, wenn eine Methode in 50% der Fälle überschrieben werden muss und ich möchte, dass die Warnung des Compilers darüber nicht überschrieben wird. Ich behebe das Problem beim Hinzufügen von Anmerkungen. Zurück zu Ihrer Frage: Es gibt zwei verschiedene Anwendungsfälle für abstrakte Klassen und Interfaces, und bisher ist noch niemand überholt.
quelle
Ich glaube nicht, dass sie durch Schnittstellen veraltet sind, aber sie könnten durch das Strategiemuster veraltet sein.
Die primäre Verwendung einer abstrakten Klasse besteht darin, einen Teil der Implementierung zu verschieben. zu sagen, "dieser Teil der Implementierung der Klasse kann anders gemacht werden".
Leider zwingt eine abstrakte Klasse den Client dazu, dies über die Vererbung zu tun . Während das Strategiemuster es Ihnen ermöglicht, dasselbe Ergebnis ohne Vererbung zu erzielen. Der Client kann Instanzen Ihrer Klasse erstellen, anstatt immer ihre eigenen zu definieren, und die "Implementierung" (das Verhalten) der Klasse kann dynamisch variieren. Das Strategiemuster hat den zusätzlichen Vorteil, dass das Verhalten nicht nur zur Entwurfszeit zur Laufzeit geändert werden kann, sondern auch eine erheblich schwächere Kopplung zwischen den beteiligten Typen aufweist.
quelle
Ich hatte im Allgemeinen weitaus mehr Wartungsprobleme mit reinen Schnittstellen als mit ABCs, selbst mit ABCs, die mehrfach vererbt wurden. YMMV - keine Ahnung, vielleicht hat unser Team sie nur unzureichend eingesetzt.
Wenn wir jedoch eine reale Analogie verwenden, wie viel Nutzen haben reine Schnittstellen, denen Funktionalität und Status völlig fehlen? Wenn ich als Beispiel USB verwende, ist das eine ziemlich stabile Schnittstelle (ich denke, wir haben jetzt USB 3.2, aber die Abwärtskompatibilität wurde beibehalten).
Es ist jedoch keine zustandslose Schnittstelle. Es ist nicht ohne Funktionalität. Es ist eher eine abstrakte Basisklasse als eine reine Schnittstelle. Es ist tatsächlich näher an einer konkreten Klasse mit sehr spezifischen Funktions- und Statusanforderungen, wobei die einzige Abstraktion das ist, was als einziger austauschbarer Teil in den Port eingesteckt wird.
Andernfalls wäre es nur eine "Lücke" in Ihrem Computer mit einem standardisierten Formfaktor und viel geringeren funktionalen Anforderungen, die nichts für sich tun würden, bis sich jeder Hersteller eine eigene Hardware ausgedacht hätte, um dieses Loch zu veranlassen, zu welchem Zeitpunkt etwas zu tun es wird ein viel schwächerer Standard und nichts weiter als ein "Loch" und eine Spezifikation dessen, was es tun soll, aber keine zentrale Bestimmung, wie es getan werden soll. In der Zwischenzeit stehen uns möglicherweise 200 verschiedene Möglichkeiten zur Verfügung, nachdem alle Hardwarehersteller versucht haben, ihre eigenen Möglichkeiten zu finden, um die Funktionalität und den Status dieser "Lücke" festzulegen.
Und zu diesem Zeitpunkt haben wir möglicherweise bestimmte Hersteller, die andere Probleme vorziehen. Wenn wir die Spezifikation aktualisieren müssen, stehen möglicherweise 200 verschiedene konkrete USB-Port-Implementierungen mit völlig unterschiedlichen Methoden zur Verfügung, um die Spezifikation zu lösen, die aktualisiert und getestet werden müssen. Einige Hersteller entwickeln möglicherweise De-facto-Standardimplementierungen, die sie untereinander teilen (Ihre analoge Basisklasse, die diese Schnittstelle implementiert), aber nicht alle. Einige Versionen sind möglicherweise langsamer als andere. Einige haben möglicherweise einen besseren Durchsatz, aber eine schlechtere Latenz oder umgekehrt. Einige verbrauchen möglicherweise mehr Batteriestrom als andere. Einige funktionieren möglicherweise nicht mit der gesamten Hardware, die mit USB-Anschlüssen kompatibel sein soll. Einige könnten erfordern, dass ein Kernreaktor angeschlossen wird, um zu arbeiten, der dazu neigt, seinen Benutzern eine Strahlenvergiftung zuzufügen.
Und das habe ich persönlich mit reinen Schnittstellen gefunden. In einigen Fällen können sie sinnvoll sein, z. B. um den Formfaktor eines Motherboards anhand eines CPU-Gehäuses zu modellieren. Formfaktor-Analogien sind in der Tat so gut wie zustandslos und funktionslos wie das analoge "Loch". Aber ich halte es oft für einen großen Fehler für Teams, das in jedem Fall als überlegen zu betrachten, nicht einmal als nahe.
Im Gegenteil, ich denke, weitaus mehr Fälle lassen sich mit ABCs besser lösen als mit Schnittstellen, wenn dies die beiden Möglichkeiten sind, es sei denn, Ihr Team ist so gigantisch, dass es eigentlich wünschenswert ist, über das analoge Äquivalent von 200 konkurrierenden USB-Implementierungen zu verfügen, anstatt über einen zentralen Standard pflegen. In einem früheren Team, in dem ich war, musste ich tatsächlich hart kämpfen, nur um den Codierungsstandard zu lockern, um ABCs und Mehrfachvererbung zu ermöglichen, und zwar hauptsächlich als Reaktion auf diese oben beschriebenen Wartungsprobleme.
quelle