Pourquoi l'utilisation de «final» sur une classe est-elle vraiment si mauvaise?

34

Je suis en train de refactoriser un site Web hérité de PHP OOP.

Je suis tellement tenté de commencer à utiliser "final" sur les classes pour " make it explicit that the class is currently not extended by anything". Cela pourrait économiser beaucoup de temps si j'arrive à une classe et je me demande si je peux renommer / supprimer / modifier une protectedpropriété ou une méthode. Si je veux vraiment étendre une classe, je peux simplement supprimer le mot-clé final pour le déverrouiller .

C'est-à-dire que si j'arrive dans une classe qui n'a pas de classe enfantine, je peux enregistrer cette connaissance en marquant la classe comme finale. La prochaine fois que j'y viendrai, je n'aurais pas à chercher à nouveau dans la base de code pour voir s'il a des enfants. Gain de temps pour les refactorings

Tout cela semble être une idée de gain de temps raisonnable ... mais j'ai souvent lu que les cours ne devraient être rendus "finaux" que lors d'occasions rares / spéciales.

Peut-être que ça gâche la création d'objet Mock ou a d'autres effets secondaires que je ne pense pas.

Qu'est-ce que je rate?

JW01
la source
3
Si vous avez le contrôle total de votre code, alors ce n'est pas terrible, je suppose, mais un peu du côté des TOC. Que se passe-t-il si vous manquez un cours (par exemple, il n’a pas d’enfants mais c’est final)? Il est préférable de laisser un outil scanner le code et de vous dire si une classe a des enfants. Dès que vous la présentez sous forme de bibliothèque et que vous la donnez à quelqu'un d'autre, il est difficile de traiter avec «final».
Job
Par exemple, MbUnit est un framework ouvert pour les tests unitaires .Net, et MsTest ne l’est pas (surprise surprise). En conséquence, vous devez débourser 5k sur MSFT simplement parce que vous voulez exécuter des tests de la manière MSFT. Les autres utilisateurs de test ne peuvent pas exécuter la DLL de test générée avec MsTest, à cause des classes finales utilisées par MSFT.
Job
3
Quelques articles de blog sur le sujet: Final Classes: ouvert aux extensions, fermé aux héritages (mai 2014; par Mathias Verraes)
hakre
Bel article. Cela parle de mes pensées à ce sujet. Je ne connaissais pas ces annotations, donc c'était pratique. À votre santé.
JW01
"PHP 5 introduit le mot-clé final, ce qui empêche les classes enfants de redéfinir une méthode en préfixant la définition par final. Si la classe elle-même est en train d'être définie comme finale, elle ne peut pas être étendue." php.net/manual/en/language.oop5.final.php C'est pas mal du tout.
Noir le

Réponses:

54

J'ai souvent lu que les cours ne devraient être «finaux» que lors d'occasions rares / spéciales.

Celui qui a écrit cela est faux. Utilisez finalgénéreusement, il n'y a rien de mal à cela. Il documente qu'une classe n'a pas été conçue avec l'héritage à l'esprit, ce qui est généralement vrai pour toutes les classes par défaut: la conception d'une classe pouvant être héritée de manière significative nécessite plus que la suppression d'un finalspécificateur; cela prend beaucoup de soin.

Donc, utiliser finalpar défaut n’est en aucun cas mauvais. En fait, beaucoup de gens proposent que ce soit le cas par défaut, par exemple Jon Skeet .

Peut-être que ça bousille la création d'objet Mock…

Ceci est certes une mise en garde, mais vous pouvez toujours recourir à des interfaces si vous devez vous moquer de vos classes. Ceci est certainement supérieur à rendre toutes les classes ouvertes à l'héritage dans le seul but de se moquer.

Konrad Rudolph
la source
1
1+: Puisqu'il bousille moqueur; envisager de créer des interfaces ou des classes abstraites pour chaque classe "finale".
Spoike
Eh bien, cela met une clé dans les œuvres. Cette arrivée tardive contredit toutes les autres réponses. Maintenant, je ne sais pas quoi croire: o. (Décoché ma réponse acceptée et retenu la décision lors du nouveau procès)
JW01
1
Vous pouvez considérer l'API Java elle-même et la fréquence à laquelle vous trouverez l'utilisation du mot clé "final". En fait, il n’est pas utilisé très souvent. Il est utilisé dans les cas où les performances pourraient devenir mauvaises en raison de la liaison dynamique qui se produit lorsque des méthodes "virtuelles" sont appelées (en Java, toutes les méthodes sont virtuelles si elles ne sont pas déclarées comme "final" ou "privé"), ou lors de l'introduction de fonctions personnalisées. Des sous-classes d'une classe d'API Java peuvent entraîner des problèmes de cohérence, tels que la sous-classe 'java.lang.String'. Une chaîne Java est un objet de valeur par définition et ne doit pas changer de valeur ultérieurement. Une sous-classe pourrait enfreindre cette règle.
Jonny Dee
5
@Jonny La plupart de l'API Java est mal conçue. N'oubliez pas que la majeure partie a plus de dix ans et que de nombreuses meilleures pratiques ont été développées entre-temps. Mais ne vous fiez pas à ma parole: les ingénieurs de Java eux-mêmes le disent, avant tout Josh Bloch, qui a écrit en détail à ce sujet, par exemple dans Effective Java . Soyez assuré que si les API Java étaient développées aujourd'hui, elles seraient très différentes et finaljoueraient un rôle beaucoup plus important.
Konrad Rudolph
1
Je ne suis toujours pas convaincu que le recours plus fréquent à «final» soit devenu une pratique exemplaire. À mon avis, le terme "final" ne devrait réellement être utilisé que si les exigences de performance le dictent, ou si vous devez vous assurer que la cohérence ne peut pas être décomposée par sous-classes. Dans tous les autres cas, la documentation nécessaire devrait aller dans, eh bien, la documentation (qui doit être écrite de toute façon), et non dans le code source en tant que mots-clés qui imposent certains modèles d'utilisation. Pour moi, c'est comme jouer à Dieu. Utiliser des annotations serait une meilleure solution. On pourrait ajouter une annotation '@final' et le compilateur pourrait émettre un avertissement.
Jonny Dee
8

Si vous voulez vous rappeler qu'une classe n'a pas de sous-classes, alors n'hésitez pas et utilisez un commentaire, c'est ce à quoi elles servent. Le mot-clé "final" n'est pas un commentaire, et utiliser des mots-clés de langage simplement pour signaler quelque chose (et vous seul savez jamais ce que cela signifie) est une mauvaise idée.

Jeremy
la source
17
C'est complètement faux! « En utilisant des mots - clés du langage juste pour signaler quelque chose à vous [...] est une mauvaise idée. » - Au contraire, c'est exactement ce que les mots clés du langage sont là pour ça . Votre mise en garde provisoire, "et vous seul saurez jamais ce que cela signifie", est bien sûr correcte mais ne s'applique pas ici; le PO utilise finalcomme prévu. Il n'y a rien de mal à ça. Et utiliser une fonctionnalité de langage pour imposer une contrainte est toujours préférable à un commentaire.
Konrad Rudolph
8
@ Konrad: Excepté finaldevrait indiquer "Aucune sous-classe de cette classe ne devrait jamais être créée" (pour des raisons juridiques ou quelque chose du genre ), pas "cette classe n'a actuellement aucun enfant, donc je suis toujours en sécurité de jouer avec ses membres protégés". L'intention de finalest l'antithèse même de "librement éditable", et une finalclasse ne devrait même pas avoir de protectedmembre!
SF.
3
@ Konrad Rudolph Ce n'est pas minuscule du tout. Si d'autres personnes souhaitent ultérieurement étendre ses cours, il existe une grande différence entre un commentaire qui dit "non étendu" et un mot clé qui dit "ne peut pas être étendu".
Jeremy
2
@ Konrad Rudolph Cela semble être une excellente opinion de poser une question sur la conception du langage POO. Mais nous savons comment fonctionne PHP, et l’autre approche consiste à autoriser une extension à moins de la sceller. Prétendre que c'est correct de combiner les mots-clés parce que "c'est comme ça que ça devrait être", c'est simplement la défensive.
Jeremy
3
@ Jeremy Je ne confond pas les mots-clés. Le mot clé finalsignifie "cette classe ne doit pas être étendue [pour le moment]". Rien de plus, rien de moins. Que PHP ait été conçu avec cette philosophie en tête est sans importance: il contient le finalmot clé, après tout. Deuxièmement, discuter de la conception de PHP est voué à l'échec, compte tenu de la conception inégale et mal conçue de PHP.
Konrad Rudolph
5

Il y a un bel article sur "Quand déclarer une classe finale" . Quelques citations:

TL; DR: Faites vos classes toujours final, si elles implémentent une interface, et aucune autre méthode publique n'est définie

Pourquoi dois-je utiliser final?

  1. Prévenir une chaîne de succession massive
  2. Composition encourageante
  3. Forcer le développeur à penser à l'API publique de l'utilisateur
  4. Forcer le développeur à réduire l'API publique d'un objet
  5. Une finalclasse peut toujours être extensible
  6. extends brise l'encapsulation
  7. Vous n'avez pas besoin de cette flexibilité
  8. Vous êtes libre de changer le code

Quand éviter final:

Les classes finales ne fonctionnent efficacement que sous les hypothèses suivantes:

  1. Il y a une abstraction (interface) que la classe finale implémente
  2. Toutes les API publiques de la classe finale font partie de cette interface

Si l'une de ces deux conditions préalables est manquante, vous arriverez probablement à un moment donné pour rendre la classe extensible, car votre code ne repose pas vraiment sur des abstractions.

PS Merci à @ocramius pour sa bonne lecture!

Nikita U.
la source
5

"final" pour une classe signifie: vous voulez une sous-classe? Allez-y, supprimez la "finale", sous-classe autant que vous le souhaitez, mais ne vous plaignez pas si cela ne fonctionne pas. Tu es tout seul.

Lorsqu'une classe peut être sous-classe, c'est un comportement sur lequel les autres s'appuient, doit être décrit en termes abstraits auxquels les sous-classes obéissent. Les appelants doivent être écrits pour s'attendre à une certaine variabilité. La documentation doit être écrite avec soin. vous ne pouvez pas dire aux gens "regardez le code source" parce que le code source n'y est pas encore. C'est tout l'effort. Si je ne m'attends pas à ce qu'une classe soit sous-classée, c'est un effort inutile. "final" dit clairement que cet effort n'a pas été fait et donne un avertissement juste.

gnasher729
la source
-1

Une chose à laquelle vous n’avez peut-être pas pensé est le fait que TOUT changement de classe signifie qu’elle doit subir de nouveaux tests d’AQ.

Ne marquez pas les choses comme définitives à moins que vous ne le pensiez vraiment.


la source
Merci. Pourriez-vous expliquer cela davantage? Voulez-vous dire que si je marque une classe comme final(un changement) alors je dois juste la tester à nouveau?
JW01
4
Je dirais ceci plus fortement. Ne marquez pas les choses comme définitives à moins qu'une extension ne puisse pas fonctionner pour cause de conception de la classe.
S.Lott
Merci, S.Lott - J'essaie de comprendre à quel point une mauvaise utilisation de final affecte le code / projet. Avez-vous vécu des histoires d'horreur qui vous ont conduit à être si dogmatique à propos de l'utilisation économe de final. Est-ce une expérience de première main?
JW01
1
@ JW01: une finalclasse a un cas d'utilisation principal. Vous avez des classes polymorphes que vous ne souhaitez pas étendre, car une sous-classe peut rompre le polymorphisme. Ne pas utiliser finalsauf si vous devez empêcher la création de sous-classes. Sinon, c'est inutile.
S.Lott
@ JW01, dans un contexte de production, il peut ne pas être autorisé à supprimer une version finale, car il ne s'agit pas du code que le client a testé et approuvé. Vous pouvez cependant être autorisé à ajouter du code supplémentaire (pour les tests), mais vous ne pourrez peut-être pas faire ce que vous voulez, car vous ne pouvez pas étendre votre classe.
-1

Utiliser 'final' enlève la liberté à ceux qui veulent utiliser votre code.

Si le code que vous écrivez est uniquement pour vous et ne sera jamais divulgué au public ou à un client, vous pouvez faire avec votre code ce que vous voulez, bien sûr. Sinon, vous empêchez les autres de tirer parti de votre code. Trop souvent, j'ai dû travailler avec une API qu'il aurait été facile d'étendre à mes besoins, mais j'ai été gênée par la «finale».

En outre, il y a souvent du code qui devrait mieux ne pas être fait private, mais protected. Bien sûr, privatesignifie "encapsulation" et masque les éléments considérés comme des détails de mise en oeuvre. Mais en tant que programmeur d'API, je pourrais aussi bien documenter le fait que la méthode xyzest considérée comme un détail d'implémentation et qu'elle peut donc être modifiée / supprimée dans une version ultérieure. Ainsi, toute personne qui compte sur ce code malgré l'avertissement le fait à ses risques et périls. Mais il peut réellement le faire et réutiliser du code (espérons-le déjà testé) et trouver plus rapidement une solution.

Bien sûr, si l'implémentation de l'API est open source, vous pouvez simplement supprimer la 'finale' ou rendre les méthodes 'protégées', mais vous avez changé le code et devez suivre vos modifications sous forme de correctifs.

Cependant, si l'implémentation est une source fermée, il vous est difficile de trouver une solution de contournement ou, dans le pire des cas, de passer à une autre API avec moins de restrictions quant aux possibilités de personnalisation / extension.

Notez que je ne trouve pas que "final" ou "private" soient diaboliques, mais je pense qu'ils sont juste utilisés trop souvent parce que le programmeur n'a pas pensé à son code en termes de réutilisation et d'extension de code.

Jonny Dee
la source
2
"L'héritage n'est pas une réutilisation de code".
quant_dev
2
Ok, si vous insistez pour une définition parfaite, vous avez raison. L'héritage exprime une relation 'est-a' et c'est ce qui permet le polymorphisme et fournit un moyen d'étendre une API. Mais en pratique, l'héritage est très souvent utilisé pour réutiliser du code. C'est particulièrement le cas avec l'héritage multiple. Et dès que vous appelez le code d’une super classe, vous le réutilisez.
Jonny Dee
@quant_dev: La réutilisation en POO signifie avant tout une polymorphie. Et l'héritage est un point critique pour le comportement polymorphe (partage de l'interface).
Falcon le
@ Interface de partage Falcon! = Code de partage. Du moins pas dans le sens habituel.
quant_dev
3
@quant_dev: la réutilisation dans le sens de la programmation orientée objet est erronée. Cela signifie qu'une seule méthode peut fonctionner sur plusieurs types de données, grâce au partage d'interface. C'est une partie importante de la réutilisation du code OOP. Et l'héritage nous permet automatiquement de partager les interfaces, améliorant ainsi considérablement la réutilisation du code. C'est pourquoi nous parlons de réutilisabilité en POO. La création de bibliothèques avec des routines est presque aussi ancienne que la programmation elle-même et peut être faite avec presque tous les langages, c'est courant. La réutilisation de code en POO est plus que cela.
Falcon