Warum ist die Verwendung von 'final' in einer Klasse wirklich so schlecht?

34

Ich überarbeite eine PHP OOP-Legacy-Website.

Ich bin so versucht, 'final' für Klassen zu " make it explicit that the class is currently not extended by anything" zu verwenden. Dies kann viel Zeit sparen, wenn ich zu einer Klasse komme und mich frage, ob ich eine protectedEigenschaft oder Methode umbenennen, löschen oder ändern kann. Wenn ich eine Klasse wirklich erweitern möchte, kann ich einfach das letzte Schlüsselwort entfernen, um es für die Erweiterung freizuschalten .

Dh wenn ich zu einer Klasse komme, die keine Kinderklassen hat, kann ich dieses Wissen aufzeichnen, indem ich der Klasse ein Finale markiere. Wenn ich das nächste Mal darauf komme, muss ich die Codebasis nicht erneut durchsuchen, um festzustellen, ob sie Kinder hat. So sparen Sie Zeit bei Umbauten.

Es scheint alles eine sinnvolle zeitsparende Idee zu sein ... aber ich habe oft gelesen, dass der Unterricht nur zu seltenen / besonderen Anlässen "endgültig" sein sollte.

Vielleicht macht es die Erstellung von Mock-Objekten kaputt oder hat andere Nebenwirkungen, an die ich nicht denke.

Was vermisse ich?

JW01
quelle
3
Wenn Sie die volle Kontrolle über Ihren Code haben, dann ist es vermutlich nicht schrecklich, aber ein bisschen auf der OCD-Seite. Was ist, wenn Sie eine Klasse verpassen (z. B. hat sie keine Kinder, ist aber noch nicht abgeschlossen)? Es ist besser, ein Tool den Code scannen zu lassen und Ihnen mitzuteilen, ob eine Klasse Kinder hat. Sobald Sie dies als Bibliothek verpacken und an eine andere Person weitergeben, wird der Umgang mit "final" zum Schmerz.
Job
Zum Beispiel ist MbUnit ein offenes Framework für .Net-Unit-Tests, und MsTest ist es nicht (Überraschung, Überraschung). Als Ergebnis MÜSSEN Sie 5k für MSFT freigeben, nur weil Sie einige Tests nach MSFT-Art ausführen möchten. Andere Testläufer können die mit MsTest erstellte Test-DLL nicht ausführen - alles wegen der von MSFT verwendeten Endklassen.
Job
3
Einige Blogbeiträge zum Thema: Final Classes: Open for Extension, Closed for Inheritance (Mai 2014; von Mathias Verraes)
hakre
Schöner Artikel. Es spricht meine Gedanken darüber. Ich wusste nichts über diese Anmerkungen, das war also praktisch. Prost.
JW01
"PHP 5 führt das Schlüsselwort final ein, mit dem verhindert wird, dass untergeordnete Klassen eine Methode überschreiben, indem der Definition das Präfix final vorangestellt wird. Wenn die Klasse selbst final definiert wird, kann sie nicht erweitert werden." php.net/manual/en/language.oop5.final.php Es ist überhaupt nicht schlecht.
Black

Antworten:

54

Ich habe oft gelesen, dass der Unterricht nur zu seltenen / besonderen Anlässen "endgültig" sein sollte.

Wer das geschrieben hat, ist falsch. Verwendung finalgroßzügig, es gibt nichts falsch mit dem. Es wird dokumentiert, dass eine Klasse nicht unter Berücksichtigung der Vererbung entworfen wurde. Dies gilt normalerweise standardmäßig für alle Klassen: Das Entwerfen einer Klasse, die sinnvoll geerbt werden kann, erfordert mehr als nur das Entfernen eines finalBezeichners. es braucht viel Sorgfalt.

So verwenden finalist standardmäßig auf keinen Fall schlecht. Tatsächlich schlagen viele Leute vor, dass dies die Standardeinstellung sein sollte, z . B. Jon Skeet .

Vielleicht macht es die Erstellung von Mock-Objekten kaputt ...

Dies ist in der Tat eine Einschränkung, aber Sie können immer auf Schnittstellen zurückgreifen, wenn Sie Ihre Klassen verspotten müssen. Dies ist sicherlich überlegen, wenn alle Klassen nur zum Zwecke des Verspottens für die Vererbung freigegeben werden.

Konrad Rudolph
quelle
1
1+: Da es vermasselt, verspotten; Erwägen Sie, Schnittstellen oder abstrakte Klassen für jede "letzte" Klasse zu erstellen.
Spoike
Nun, das ist ein Schlüssel in den Werken. Diese späte Ankunft widerspricht allen anderen Antworten. Jetzt weiß ich nicht was ich glauben soll: o. (Heben Sie die Markierung meiner akzeptierten Antwort auf und halten Sie die Entscheidung während des erneuten Prozesses zurück.)
JW01
1
Sie können die Java-API selbst betrachten und wie oft Sie die Verwendung des Schlüsselworts 'final' finden. In der Tat wird es nicht sehr oft verwendet. Es wird in Fällen verwendet, in denen die Leistung aufgrund der dynamischen Bindung, die beim Aufrufen von "virtuellen" Methoden stattfindet (in Java sind alle Methoden virtuell, wenn sie nicht als "final" oder "privat" deklariert sind), oder bei der Einführung von benutzerdefinierten Methoden beeinträchtigt werden kann Unterklassen einer Java-API-Klasse können Konsistenzprobleme verursachen, z. B. die Unterklasse 'java.lang.String'. Ein Java-String ist ein Wertobjekt per Definition und darf seinen Wert später nicht mehr ändern. Eine Unterklasse könnte gegen diese Regel verstoßen.
Jonny Dee
5
@Jonny Der Großteil der Java-API ist schlecht gestaltet. Denken Sie daran, dass der Großteil davon mehr als zehn Jahre alt ist und in der Zwischenzeit viele bewährte Methoden entwickelt wurden. Aber nehmen Sie nicht mein Wort: Das sagen die Java-Ingenieure selbst, allen voran Josh Bloch, der ausführlich darüber geschrieben hat, z . B. in Effective Java . Seien Sie versichert, dass die Java-API, wenn sie heute entwickelt würde, ganz anders aussehen und finaleine viel größere Rolle spielen würde.
Konrad Rudolph
1
Ich bin immer noch nicht davon überzeugt, dass die häufigere Verwendung von "final" eine bewährte Methode geworden ist. Meiner Meinung nach sollte das "Finale" nur dann wirklich verwendet werden, wenn es die Leistungsanforderungen erfordern oder wenn Sie sicherstellen müssen, dass die Konsistenz nicht durch Unterklassen gebrochen werden kann. In allen anderen Fällen sollte die erforderliche Dokumentation in die Dokumentation (die ohnehin geschrieben werden muss) und nicht in den Quellcode als Schlüsselwort eingefügt werden, das bestimmte Verwendungsmuster erzwingt. Für mich ist das eine Art Gott zu spielen. Anmerkungen zu verwenden, wäre eine bessere Lösung. Man könnte eine '@ final'-Annotation hinzufügen und der Compiler könnte eine Warnung ausgeben.
Jonny Dee
8

Wenn Sie sich selbst eine Notiz machen möchten, dass eine Klasse keine Unterklassen hat, dann tun Sie dies auf jeden Fall und verwenden Sie einen Kommentar, dafür sind sie da. Das "letzte" Schlüsselwort ist kein Kommentar, und die Verwendung von Sprachschlüsselwörtern, um Ihnen etwas zu signalisieren (und nur Sie würden jemals wissen, was es bedeutet), ist eine schlechte Idee.

Jeremy
quelle
17
Das ist völlig falsch! „Nur Sprachschlüsselwörter zu verwenden, um Ihnen etwas zu signalisieren […], ist eine schlechte Idee.“ - Im Gegenteil, genau dafür gibt es Sprachschlüsselwörter. Ihre vorläufige Einschränkung, "und nur Sie würden jemals wissen, was es bedeutet", ist natürlich richtig, gilt aber hier nicht; Das OP wird finalwie erwartet verwendet. Daran ist nichts auszusetzen. Die Verwendung einer Sprachfunktion zum Erzwingen einer Einschränkung ist der Verwendung eines Kommentars immer überlegen.
Konrad Rudolph
8
@Konrad: Außer finalsollte "Keine Unterklasse dieser Klasse sollte jemals erstellt werden" (aus rechtlichen Gründen oder so), nicht "Diese Klasse hat derzeit keine Kinder, daher kann ich mich immer noch sicher mit ihren geschützten Mitgliedern anlegen". Die Absicht von finalist das Gegenteil von "frei editierbar", und eine finalKlasse sollte nicht einmal protectedMitglieder haben!
SF.
3
@Konrad Rudolph Es ist überhaupt nicht winzig. Wenn andere Personen später ihre Klassen erweitern möchten, gibt es einen großen Unterschied zwischen einem Kommentar mit der Aufschrift "Nicht erweitert" und einem Schlüsselwort mit der Aufschrift "Möglicherweise nicht erweitert".
Jeremy
2
@Konrad Rudolph Das klingt nach einer großartigen Meinung, um eine Frage zum OOP-Sprachdesign zu stellen. Wir wissen jedoch, wie PHP funktioniert, und es ist der andere Ansatz, eine Erweiterung zuzulassen, sofern diese nicht versiegelt ist. Die Behauptung, dass es in Ordnung ist, Keywords zu verschmelzen, weil "so sollte es sein" nur Verteidigung ist.
Jeremy
3
@ Jeremy Ich bin nicht konfligierende Keywords. Das Schlüsselwort finalbedeutet: „Diese Klasse darf [vorerst] nicht erweitert werden.“ Nicht mehr und nicht weniger. Ob PHP unter Berücksichtigung dieser Philosophie entwickelt wurde, ist unerheblich: Es hat schließlich das finalSchlüsselwort. Zweitens wird das Argumentieren von PHPs Design scheitern, wenn man bedenkt, wie patchworkig und insgesamt schlecht PHP gestaltet ist.
Konrad Rudolph
5

Es gibt einen schönen Artikel über "Wann sind die Klassen für endgültig zu erklären?" . Ein paar Zitate daraus:

TL; DR: Machen Sie Ihre Klassen immer final, wenn sie eine Schnittstelle implementieren und keine anderen öffentlichen Methoden definiert sind

Warum muss ich verwenden final?

  1. Verhinderung einer massiven Vererbungskette des Schicksals
  2. Ermutigende Komposition
  3. Erzwingen Sie, dass der Entwickler über die öffentliche API des Benutzers nachdenkt
  4. Erzwingen Sie, dass der Entwickler die öffentliche API eines Objekts verkleinert
  5. Eine finalKlasse kann jederzeit erweiterbar gemacht werden
  6. extends bricht die Kapselung
  7. Diese Flexibilität brauchen Sie nicht
  8. Sie können den Code jederzeit ändern

Wann zu vermeiden final:

Die Abschlussklassen funktionieren nur unter folgenden Voraussetzungen:

  1. Es gibt eine Abstraktion (Schnittstelle), die die letzte Klasse implementiert
  2. Die gesamte öffentliche API der letzten Klasse ist Teil dieser Schnittstelle

Wenn eine dieser beiden Voraussetzungen fehlt, werden Sie wahrscheinlich einen Zeitpunkt erreichen, an dem Sie die Klasse erweiterbar machen, da sich Ihr Code nicht wirklich auf Abstraktionen stützt.

PS Danke an @ocramius für die großartige Lektüre!

Nikita U.
quelle
5

"final" für eine Klasse bedeutet: Sie wollen eine Unterklasse? Gehen Sie weiter, löschen Sie die "letzte" Unterklasse so oft Sie möchten, aber beschweren Sie sich nicht bei mir, wenn es nicht funktioniert. Du bist auf dich allein gestellt.

Wenn eine Klasse in Unterklassen unterteilt werden kann, muss das Verhalten, auf das sich andere verlassen, in abstrakten Begriffen beschrieben werden, die Unterklassen gehorchen. Anrufer müssen geschrieben sein, um eine gewisse Variabilität zu erwarten. Dokumentation muss sorgfältig geschrieben werden; Sie können den Leuten nicht sagen, dass sie sich den Quellcode ansehen sollen, da der Quellcode noch nicht vorhanden ist. Das ist alles Anstrengung. Wenn ich nicht erwarte, dass eine Klasse einer Unterklasse angehört, ist das unnötiger Aufwand. "final" sagt deutlich, dass diese Anstrengung nicht unternommen wurde und gibt eine faire Warnung.

gnasher729
quelle
-1

Eine Sache, an die Sie vielleicht nicht gedacht haben, ist die Tatsache, dass JEDER Klassenwechsel bedeutet, dass er neuen QS-Tests unterzogen werden muss.

Kennzeichnen Sie Dinge nur dann als endgültig, wenn Sie es wirklich ernst meinen.


quelle
Vielen Dank. Können Sie das näher erläutern? Meinen Sie, wenn ich eine Klasse als final(eine Änderung) markiere, muss ich sie nur erneut testen?
JW01
4
Ich würde das stärker sagen. Markieren Sie Dinge nicht als endgültig, es sei denn, eine Erweiterung kann aufgrund des Designs der Klasse nicht zum Funktionieren gebracht werden.
S.Lott
Danke S.Lott - Ich versuche zu verstehen, wie sich die schlechte Verwendung von final auf den Code / das Projekt auswirkt. Haben Sie irgendwelche schlechten Horrorgeschichten erlebt , die Sie führen so dogmatisch zu sein über den sparsamen Einsatz von final. Ist das Erfahrung aus erster Hand?
JW01
1
@ JW01: Eine finalKlasse hat einen primären Anwendungsfall. Sie haben polymorphe Klassen, die Sie nicht erweitern möchten, da eine Unterklasse den Polymorphismus unterbrechen kann. Verwenden finalSie diese Option nur, wenn Sie die Erstellung von Unterklassen verhindern müssen . Davon abgesehen ist es nutzlos.
S.Lott
@ JW01 In einer Produktionseinstellung ist es möglicherweise nicht ERLAUBT , ein Finale zu entfernen, da dies nicht der Code ist, den der Kunde getestet und genehmigt hat. Möglicherweise können Sie jedoch zusätzlichen Code hinzufügen (zum Testen), aber möglicherweise können Sie nicht das tun, was Sie möchten, da Sie Ihre Klasse nicht erweitern können.
-1

Die Verwendung von "final" nimmt anderen, die Ihren Code verwenden möchten, die Freiheit.

Wenn der von Ihnen geschriebene Code nur für Sie bestimmt ist und niemals für die Öffentlichkeit oder einen Kunden freigegeben wird, können Sie mit Ihrem Code natürlich tun, was Sie wollen. Andernfalls verhindern Sie, dass andere auf Ihrem Code aufbauen. Zu oft musste ich mit einer API arbeiten, die sich für meine Bedürfnisse leicht erweitern ließ, aber dann wurde ich durch "final" behindert.

Auch gibt es oft Code, der besser nicht gemacht werden sollte private, aber protected. Sicher, privatebedeutet "Kapselung" und versteckt Dinge, die als Implementierungsdetails angesehen werden. Als API-Programmierer kann ich jedoch auch die Tatsache dokumentieren, dass die Methode xyzals Implementierungsdetail betrachtet wird und daher in zukünftigen Versionen möglicherweise geändert / gelöscht wird. Jeder, der sich trotz der Warnung auf solchen Code verlässt, tut dies auf eigenes Risiko. Aber er kann es tatsächlich tun und den (hoffentlich bereits getesteten) Code wiederverwenden und schneller eine Lösung finden.

Wenn die API-Implementierung Open Source ist, kann man natürlich einfach die 'final' entfernen oder Methoden 'protected' machen, aber dann haben Sie den Code geändert und müssen Ihre Änderungen in Form von Patches nachverfolgen.

Wenn es sich bei der Implementierung jedoch um eine Closed-Source-Implementierung handelt, müssen Sie keine Problemumgehung finden oder im schlimmsten Fall auf eine andere API mit weniger Einschränkungen hinsichtlich der Anpassungs- / Erweiterungsmöglichkeiten wechseln.

Beachten Sie, dass ich nicht finde, dass 'final' oder 'private' böse sind, aber ich denke, sie werden einfach zu oft verwendet, weil der Programmierer nicht über seinen Code nachgedacht hat, was die Wiederverwendung und Erweiterung von Code betrifft.

Jonny Dee
quelle
2
"Vererbung ist keine Wiederverwendung von Code".
quant_dev
2
Ok, wenn Sie auf einer perfekten Definition bestehen, haben Sie Recht. Die Vererbung drückt eine 'Ist-Ein'-Beziehung aus. Diese Tatsache ermöglicht Polymorphie und die Erweiterung einer API. In der Praxis wird die Vererbung jedoch sehr häufig zur Wiederverwendung von Code verwendet. Dies ist insbesondere bei Mehrfachvererbung der Fall. Und sobald Sie Code einer Superklasse aufrufen, verwenden Sie tatsächlich Code wieder.
Jonny Dee
@quant_dev: Wiederverwendbarkeit in OOP bedeutet vor allem Polymorphie. Und Vererbung ist ein kritischer Punkt für polymorphes Verhalten (gemeinsame Nutzung der Schnittstelle).
Falcon
@ Falcon Sharing Interface! = Code teilen. Zumindest nicht im üblichen Sinne.
quant_dev
3
@quant_dev: Wiederverwendbarkeit im OOP-Sinne ist falsch. Dies bedeutet, dass eine einzige Methode dank gemeinsamer Schnittstellen viele Datentypen verarbeiten kann. Das ist ein wesentlicher Teil der Wiederverwendung von OOP-Code. Durch Vererbung können wir automatisch Schnittstellen freigeben, wodurch die Wiederverwendbarkeit von Code erheblich verbessert wird. Deshalb sprechen wir über Wiederverwendbarkeit in OOP. Das Erstellen von Bibliotheken mit Routinen ist fast so alt wie das Programmieren selbst und kann mit fast jeder Sprache durchgeführt werden. Code-Reuse in OOP ist mehr als das.
Falcon