Warum ist die Unterklasse zu schlecht (und warum sollten wir Prototypen verwenden, um sie zu beseitigen)?

10

Ich habe über Entwurfsmuster nachgelesen und gelesen, dass das Prototyp-Entwurfsmuster übermäßige Unterklassen beseitigt.

Warum ist die Unterklasse schlecht? Welchen Vorteil würde die Verwendung eines Prototyps gegenüber der Unterklasse haben?

David Faux
quelle
Sagt wer ? Hast du einen Link?
Camus
Ich habe S.118 dieses Buches gelesen. goo.gl/6JGw1
David Faux

Antworten:

19

Warum ist die Unterklasse zu schlecht?

"Zu viel" ist ein Urteilsspruch; aber nimm es von mir, es ist schlecht. Der Code, mit dem ich arbeite, hat DEEP-Vererbung und der Code ist verdammt schwer zu lesen, zu verstehen, zu folgen, zu verfolgen, zu debuggen usw. usw. Es ist praktisch unmöglich, Testcode für dieses Zeug zu schreiben.

Das Prototypmuster beseitigt die Unterklasse

Oder bedeutet die Frage "beseitigt zu viele Unterklassen?". Dieses Muster erfordert das Klonen eines "Vorlagen" -Objekts, um zumindest zu diesem Zeitpunkt eine Unterklasse zu vermeiden. Es gibt keine Regel, die besagt, dass die "Vorlage" keine Unterklasse sein kann.

Bevorzugen Sie die Komposition gegenüber der Vererbung

Diese Idee umfasst hier auch die Delegierung und Aggregation. Wenn Sie dieser Heuristik folgen, ist Ihre Software in der Regel flexibler, einfacher zu warten, zu erweitern und wiederzuverwenden.

Wenn eine Klasse aus Teilen besteht , können Sie diese Teile zur Laufzeit ersetzen . Dies hat tiefgreifende Auswirkungen auf die Testbarkeit.

Testen ist einfacher . Sie können gefälschte Teile verwenden (dh "Mocks", "Doubles" und andere Testgespräche). Die tiefe Vererbung unseres Codes bedeutet, dass wir die gesamte Hierarchie instanziieren müssen, um etwas davon zu testen. In unserem Fall ist dies nicht möglich, ohne den Code in seiner realen Umgebung auszuführen. Zum Beispiel benötigen wir eine Datenbank, um Geschäftsobjekte zu instanziieren.

Änderungen sind mit Nebenwirkungen und Unsicherheiten verbunden. Je mehr "Basis" die Klasse hat, desto weiter verbreitet sind die Auswirkungen, ob gut oder schlecht. Möglicherweise sind Änderungen erwünscht, die Sie aufgrund der Unsicherheit der Nebenwirkungen nicht vornehmen dürfen. Oder eine Änderung, die für einen bestimmten Ort in unserer Vererbungskette gut ist, ist für einen anderen schlecht. Dies ist sicherlich meine Erfahrung.

Radarbob
quelle
Es würde mich sehr interessieren, einige Beispiele dafür zu sehen, wann Vererbung das Testen (insbesondere das Verspotten) schwierig macht und wie Komposition dabei hilft.
Armand
@alison: Eine Methode in einer abgeleiteten Klasse, die eine andere Methode aus der Basisklasse verwendet, bei der die Implementierung in der Basisklasse das Testen von Fehlerszenarien praktisch unmöglich macht. Wäre dies eine Komposition gewesen, wäre es einfacher, ein Objekt zu injizieren, das den Fehler verursacht
Rune FS
@allison: Beispiele? Wenn die Codebasis sehr groß ist, wenn Basisklassen von Dutzenden von Klassen direkt und Hunderten durch Vererbung verwendet werden. Da der Punkt der tiefen Vererbung die Wiederverwendung ist und eine Basisklasse so weit generiert wird, dass der Debugger-Stack-Trace nicht mehr anzeigen kann, welche Methoden tatsächlich aufgerufen werden. Schließlich, wenn in einem solchen Frankenstein noch keine Abhängigkeitsinjektion eingebaut ist. Eine Klasse der obersten Ebene kann ein halbes Dutzend oder mehr andere Dinge sein, die zusammen Dutzende interner Objekte "haben" - jedes mit seinem eigenen Erbspaßhaus.
Radarbob
8

Ich denke, einer der Gründe, warum dies als schlechte Praxis angesehen wurde, ist, dass jede Unterklasse im Laufe der Zeit Attribute und Methoden enthalten kann, die für dieses Objekt keinen Sinn ergeben, je weiter Sie die Kette entlang gehen (in einer schlecht entwickelten Hierarchie). Sie können mit einer aufgeblähten Klasse enden, die Elemente enthält, die für diese Klasse keinen Sinn ergeben.

Ich bin selbst Agnostiker. Es scheint dogmatisch zu sein, nur zu sagen, dass es schlecht ist. Alles ist schlecht, wenn es missbraucht wird.

jmq
quelle
2

Zwei Gründe.

  1. Ist Laufzeitleistung. Jedes Mal, wenn Sie eine Unterklasse aus einer Oberklasse aufrufen, führen Sie einen Methodenaufruf durch. Wenn Sie also fünf Klassen verschachtelt und eine Methode in der Basisklasse aufgerufen haben, führen Sie fünf Methodenaufrufe durch. Die meisten Compiler / Laufzeiten werden versuchen, dies zu erkennen und die zusätzlichen Anrufe zu optimieren, aber es ist nur in sehr einfachen Fällen sicher, dies zu tun.

  2. Ist die Vernunft Ihrer Programmierkollegen. Wenn Sie fünf Klassen verschachteln, muss ich alle vier übergeordneten Klassen untersuchen, um festzustellen, dass Sie wirklich eine Methode in der Basisklasse aufrufen. Dies wird mühsam. Möglicherweise muss ich beim Debuggen des Programms auch fünf Methodenaufrufe durchlaufen.

James Anderson
quelle
6
-1: Sie haben Recht mit # 2; aber nicht über # 1. Mehrere Vererbungsebenen wirken sich nicht unbedingt auf die Laufzeitleistung aus.
Jim G.
Besorgt über ein paar zusätzliche Prozeduraufrufe auf Maschinencode-Ebene und die Verwendung von OP und Vererbung, und Sie sorgen sich um die Vernunft Ihrer Programmierkollegen .....
mattnz
@ JimG. Es mag nicht, aber es kann. :) Es gibt auch eine mögliche Speichernutzung, über die Sie sich Sorgen machen müssen: Wenn Sie nicht alle Variablen der Basis wiederverwenden, werden sie möglicherweise weiterhin als Teil der Erstellung des Objekts zugewiesen.
Adam Lear
2

"Zu viel" ist ein Urteilsspruch.

Wie bei den meisten Dingen in der Programmierung ist die Unterklassifizierung nicht von Natur aus schlecht, wenn sie sinnvoll ist. Wenn das Modell Ihrer Problemlösung als Hierarchie und nicht als Zusammensetzung von Teilen sinnvoller ist, kann die Unterklasse die bevorzugte Option sein.

Die Bevorzugung der Komposition gegenüber der Vererbung ist jedoch eine gute Faustregel.

Wenn Sie eine Unterklasse bilden, kann das Testen schwieriger sein (wie Radarbob feststellt ). In einer tiefen Hierarchie ist es schwieriger, problematischen Code zu isolieren, da für die Instanziierung einer Unterklasse die gesamte Oberklassenhierarchie und eine Reihe zusätzlicher Codes erforderlich sind. Mit einem kompositorischen Ansatz können Sie jede Komponente Ihres Aggregatobjekts mit Komponententests, Scheinobjekten usw. isolieren und testen.

Unterklassen können auch dazu führen, dass Sie Details Ihrer Implementierung offenlegen, die Sie möglicherweise nicht möchten. Dies macht es schwierig, den Zugriff auf die zugrunde liegende Darstellung Ihrer Daten zu steuern, und bindet Sie an eine bestimmte Implementierung Ihres Sicherungsmodells.

Ein Beispiel, an das ich denken kann, ist java.util.Properties, das von Hashtable erbt. Die Properties-Klasse dient nur zum Speichern von String-String-Paaren (dies wird in den Javadocs vermerkt). Aufgrund der Vererbung wurden die Methoden put und putAll von Hashtable Benutzern von Eigenschaften zur Verfügung gestellt. Während die "richtige" Art, eine Eigenschaft zu speichern, setProperty () ist, hindert absolut nichts einen Benutzer daran, put () aufzurufen und einen String und ein Objekt zu übergeben.

Grundsätzlich ist die Semantik von Eigenschaften aufgrund der Vererbung schlecht definiert. Sollte jemand jemals das Hintergrundobjekt für Eigenschaften ändern wollen, hat die Auswirkung einen viel größeren Einfluss auf Code, der Eigenschaften verwendet, als wenn Eigenschaften einen kompositorischeren Ansatz gewählt hätten.

seggy
quelle
2

Vererbung kann in einigen Fällen die Kapselung unterbrechen, und wenn dies passiert, ist es schlecht. Lesen Sie mehr.


In guten alten Zeiten ohne Vererbung veröffentlichte eine Bibliothek eine API, und eine Anwendung, die sie verwendet, nutzt das, was öffentlich sichtbar ist, und die Bibliothek hat jetzt ihre eigene Methode, um mit den internen Zahnrädern umzugehen, die sich ständig ändern, ohne die App zu beschädigen.

Wenn sich die Anforderungen ändern, kann sich die Bibliothek weiterentwickeln und mehr Kunden haben. oder es kann erweiterte Funktionen bereitstellen, indem es mit anderen Varianten von seiner eigenen Klasse erbt. So weit, ist es gut.

Grundlegend ist jedoch, dass eine Unterklasse, wenn eine Anwendung die Klasse aufnimmt und beginnt, sie zu unterklassifizieren, tatsächlich eher ein Client als ein interner Partner ist. Im Gegensatz zu einer eindeutigen Definition der öffentlichen API befindet sich die Unterklasse jetzt jedoch viel tiefer in der Bibliothek (Zugriff auf alle privaten Variablen usw.). Es ist noch nicht schlecht, aber ein böser Charakter kann eine API überbrücken, die sowohl in der APP als auch in der Bibliothek zu viel ist.

Im Wesentlichen ist dies zwar nicht immer der Fall, aber

Es ist leicht, die Vererbung zu missbrauchen, um die Kapselung zu unterbrechen

Das Zulassen von Unterklassen kann in diesem Punkt falsch sein.

Dipan Mehta
quelle
1

Kurze schnelle Antwort:

Es hängt davon ab, was Sie tun.

Erweiterte langweilige Antwort:

Ein Entwickler kann eine App erstellen. Verwenden von Vorlagen für Unterklassen oder (Prototypen). Manchmal funktioniert eine Technik besser. Manchmal funktioniert die andere Technik besser.

Die Auswahl mit der Technik funktioniert am besten und erfordert auch die Prüfung, ob ein Szenario mit "Mehrfachvererbung" vorliegt. Auch wenn die Programmiersprache nur Einzelvererbung, Vorlagen oder Prototypen unterstützt.

Ergänzende Anmerkung:

Einige Antworten beziehen sich auf die "Mehrfachvererbung". Altought, nicht die Hauptfrage, es hängt mit dem Thema zusammen. Es gibt mehrere ähnliche Beiträge zum Thema "Mehrfachvererbung vs. Zusammensetzung". Ich hatte einen Fall, in dem ich beide mische.

umlcat
quelle