Il semble que Java ait eu le pouvoir de déclarer des classes non-dérivables depuis des siècles, et maintenant, C ++ l’a aussi. Cependant, à la lumière du principe Open / Close de SOLID, pourquoi cela serait-il utile? Pour moi, le final
mot - clé sonne juste friend
- il est légal, mais si vous l'utilisez, le design est probablement faux. Veuillez fournir quelques exemples dans lesquels une classe non-dérivable ferait partie d'une excellente architecture ou modèle de conception.
54
final
? Beaucoup de gens (y compris moi-même) trouvent que c'est un bon design pour faire chaque classe non abstraitefinal
.final
.Réponses:
final
exprime l'intention . Il indique à l'utilisateur d'une classe, d'une méthode ou d'une variable "Cet élément n'est pas censé changer, et si vous souhaitez le modifier, vous n'avez pas compris la conception existante."Ceci est important car l'architecture du programme serait très difficile si vous deviez anticiper que chaque classe et chaque méthode que vous écrivez pourraient être modifiées pour faire quelque chose de complètement différent avec une sous-classe. Il est beaucoup mieux de décider dès le départ quels éléments sont supposés être modifiables et lesquels ne le sont pas, et de faire en sorte que rien ne change
final
.Vous pouvez également le faire via des commentaires et des documents d’architecture, mais il est toujours préférable de laisser le compilateur faire respecter ce qu’il peut, au lieu d’espérer que les futurs utilisateurs liront et respecteront la documentation.
la source
Cela évite le problème de la classe de base fragile . Chaque classe est livrée avec un ensemble de garanties et d'invariants implicites ou explicites. Le principe de substitution de Liskov stipule que tous les sous-types de cette classe doivent également fournir toutes ces garanties. Cependant, il est vraiment facile de violer cela si nous n'utilisons pas
final
. Par exemple, établissons un vérificateur de mot de passe:Si nous permettons à cette classe d'être remplacée, une implémentation pourrait verrouiller tout le monde, une autre pourrait donner à tout le monde l'accès:
Cela n’est généralement pas acceptable, car les sous-classes ont maintenant un comportement très incompatible avec le comportement initial. Si nous souhaitons réellement que la classe soit étendue à un autre comportement, une chaîne de responsabilité serait préférable:
Le problème devient plus évident quand plus d'une classe compliquée appelle ses propres méthodes, et ces méthodes peuvent être remplacées. Je rencontre parfois cela lorsque joliment imprimer une structure de données ou écrire du HTML. Chaque méthode est responsable d'un widget.
Je crée maintenant une sous-classe qui ajoute un peu plus de style:
Ignorant pour l'instant que ce n'est pas un très bon moyen de générer des pages HTML, que se passe-t-il si je souhaite modifier à nouveau la mise en page? Je devrais créer une
SpiffyPage
sous-classe qui en quelque sorte encapsule ce contenu. Ce que nous pouvons voir ici est une application accidentelle du modèle de méthode template. Les méthodes de modèle sont des points d'extension bien définis dans une classe de base destinés à être remplacés.Et que se passe-t-il si la classe de base change? Si le contenu HTML change trop, cela pourrait endommager la mise en page fournie par les sous-classes. Il n’est donc pas très prudent de changer de classe de base par la suite. Cela n'est pas évident si toutes vos classes sont dans le même projet, mais très visible si la classe de base fait partie d'un logiciel publié sur lequel d'autres personnes s'appuient.
Si cette stratégie d’extension était prévue, nous aurions pu permettre à l’utilisateur d’échanger la manière dont chaque partie est générée. Soit une stratégie pour chaque bloc pouvant être fournie à l’extérieur. Ou, nous pourrions nidifier les décorateurs. Cela serait équivalent au code ci-dessus, mais beaucoup plus explicite et beaucoup plus flexible:
(Le
top
paramètre supplémentaire est nécessaire pour garantir que les appelswriteMainContent
passent par le haut de la chaîne de décorateurs. Cela émule une fonctionnalité de sous-classement appelée récursion ouverte .)Si nous avons plusieurs décorateurs, nous pouvons maintenant les mélanger plus librement.
Bien plus souvent que le désir d'adapter légèrement les fonctionnalités existantes est le désir de réutiliser une partie d'une classe existante. J'ai vu un cas où quelqu'un voulait une classe où vous pouvez ajouter des éléments et les parcourir tous. La bonne solution aurait été de:
Au lieu de cela, ils ont créé une sous-classe:
Cela signifie soudainement que toute l'interface de
ArrayList
est devenue une partie de notre interface. Les utilisateurs peuvent desremove()
choses, ou desget()
choses à des index spécifiques. C'était prévu comme ça? D'ACCORD. Mais souvent, nous ne réfléchissons pas soigneusement à toutes les conséquences.Il est donc conseillé de
extend
une classe sans réflexion.final
sauf si vous souhaitez que l'une des méthodes soit remplacée.Il existe de nombreux exemples dans lesquels cette «règle» doit être enfreinte, mais elle vous guide généralement vers une conception flexible et de bonne qualité, et évite les bogues dus à des modifications inattendues dans les classes de base (ou à des utilisations non intentionnelles de la sous-classe en tant qu'instance de la classe de base. ).
Certaines langues ont des mécanismes d'application plus stricts:
virtual
la source
Je suis surpris que personne n'ait encore mentionné Effective Java, 2e édition de Joshua Bloch (qui devrait au moins être lu par tous les développeurs Java). Le point 17 du livre traite de cette question en détail et s'intitule: " Concevez et documentez en vue de l'héritage ou interdisez-le ".
Je ne répéterai pas tous les bons conseils du livre, mais ces paragraphes semblent pertinents:
la source
L’une des raisons
final
est utile, c’est que cela garantit que vous ne pouvez pas sous-classer une classe de manière à violer le contrat de la classe parente. Un tel sous-classement constituerait une violation de SOLID (surtout "L") et le fait qu’une classe l’final
empêche.Un exemple typique consiste à rendre impossible la sous-classe d'une classe immuable d'une manière qui rendrait la sous-classe mutable. Dans certains cas, un tel changement de comportement peut avoir des effets très surprenants, par exemple lorsque vous utilisez quelque chose comme clé dans une carte en pensant que la clé est immuable, alors qu'en réalité, vous utilisez une sous-classe qui est modifiable.
En Java, beaucoup de problèmes de sécurité intéressants pourraient être introduits si vous pouviez sous
String
- classer et le rendre mutable (ou le rappeler à la maison quand quelqu'un appelle ses méthodes, extrayant ainsi éventuellement des données sensibles du système) à mesure que ces objets sont passés. autour d’un code interne lié au chargement et à la sécurité des classes.Final est aussi parfois utile pour éviter des erreurs simples telles que la réutilisation de la même variable pour deux choses dans une méthode, etc. Dans Scala, il est conseillé d'utiliser uniquement la
val
variable correspondant approximativement aux variables finales en Java, et en réalité toute utilisation d'unvar
ou variable non finale est regardé avec suspicion.Enfin, les compilateurs peuvent, du moins en théorie, effectuer des optimisations supplémentaires s’ils savent qu’une classe ou une méthode est finale, car lorsque vous appelez une méthode sur une classe finale, vous savez exactement quelle méthode sera appelée et vous n’aurez pas à y aller. via la table de méthode virtuelle pour vérifier l'héritage.
la source
SecurityManager
. La plupart des programmes ne l'utilisent pas, mais ils n'exécutent également aucun code non approuvé. +++ Vous devez supposer que les chaînes sont immuables, sinon vous n'obtiendrez aucune sécurité et un tas de bogues arbitraires en prime. Tout programmeur qui suppose que les chaînes Java peuvent changer en code productif est certainement fou.La deuxième raison est la performance . La première raison est que certaines classes ont des comportements ou des états importants qui ne sont pas supposés être modifiés pour permettre au système de fonctionner. Par exemple, si j'ai une classe "PasswordCheck" et pour la construire, j'ai embauché une équipe d'experts en sécurité et cette classe communique avec des centaines de distributeurs automatiques de billets avec des procols bien étudiés et définis. Permettre à un nouvel employé récemment sorti de l'université de créer une classe "TrustMePasswordCheck" qui étend la classe ci-dessus pourrait être très préjudiciable pour mon système; ces méthodes ne sont pas censées être remplacées, c'est tout.
la source
Quand j'ai besoin d'un cours, j'écris un cours. Si je n'ai pas besoin de sous-classes, je m'en fiche des sous-classes. Je m'assure que ma classe se comporte comme prévu et que les endroits où j'utilise la classe supposent que la classe se comporte comme prévu.
Si quelqu'un veut sous-classer ma classe, je veux nier toute responsabilité pour ce qui se passe. J'y parviens en rendant la classe "finale". Si vous voulez le sous-classer, rappelez-vous que je n’ai pas pris en compte le sous-groupe pendant que j’écrivais le cours. Vous devez donc prendre le code source de la classe, supprimer le "final", et à partir de là, tout ce qui se passe relève de votre entière responsabilité .
Vous pensez que c'est "pas orienté objet"? J'étais payé pour faire un cours qui fasse ce qu'il est censé faire. Personne ne m'a payé pour avoir fait un cours qui pourrait être subdivisé. Si vous êtes payé pour rendre ma classe réutilisable, vous pouvez le faire. Commencez par supprimer le mot clé "final".
(Autre que cela, "final" permet souvent des optimisations substantielles. Par exemple, dans Swift "final" sur une classe publique, ou sur une méthode d'une classe publique, signifie que le compilateur sait parfaitement quel code un appel de méthode va exécuter, et peut remplacer l’envoi dynamique par l’envoi statique (avantage minime) et remplace souvent l’envoi statique par un alignement (avantage potentiellement énorme)).
adelphus: Qu'est-ce qui est si difficile à comprendre à propos de "si vous voulez le sous-classer, prenez le code source, supprimez le" final ", et c'est votre responsabilité"? "final" équivaut à "fair warning".
Et je ne suis pas payé pour créer du code réutilisable. Je suis payé pour écrire du code qui fait ce qu'il est censé faire. Si je suis payé pour créer deux morceaux de code similaires, j'en extrait les parties communes parce que c'est moins cher et que je ne suis pas payé pour perdre mon temps. Rendre le code réutilisable qui ne soit pas réutilisé est une perte de temps.
M4ks: Vous faites toujours tout ce qui est privé et qui n'est pas censé être accessible de l'extérieur. Encore une fois, si vous voulez créer une sous-classe, vous prenez le code source, modifiez les éléments en "protégé" si vous en avez besoin, et prenez la responsabilité de ce que vous faites. Si vous pensez avoir besoin d'accéder à des choses que j'ai qualifiées de privées, vous devez savoir ce que vous faites.
Les deux: Le sous-classement est une infime partie du code de réutilisation. Créer des blocs de construction qui peuvent être adaptés sans sous-classe est beaucoup plus puissant et profite énormément de "final", car les utilisateurs des blocs peuvent compter sur ce qu'ils obtiennent.
la source
Imaginons que le SDK d'une plate-forme embarque la classe suivante:
Une application sous-classe cette classe:
Tout va bien, mais quelqu'un qui travaille sur le SDK décide qu'il
get
est ridicule de transmettre une méthode à l'interface et améliore l'interface en veillant à ce que la compatibilité en amont soit appliquée.Tout semble aller bien, jusqu'à ce que l'application d'en haut soit recompilée avec le nouveau SDK. Soudainement, la méthode get overriden n'est plus appelée et la demande n'est pas comptabilisée.
C'est ce qu'on appelle le problème de la classe de base fragile, car un changement apparemment inoffensif entraîne la rupture d'une sous-classe. Toute modification des méthodes appelées à l'intérieur de la classe peut entraîner la rupture d'une sous-classe. Cela tend à dire que presque tous les changements peuvent causer la rupture d’une sous-classe.
Final empêche quiconque de sous-classer votre classe. De cette façon, quelles méthodes de la classe peuvent être modifiées sans se soucier du fait que, quelque part, quelqu'un dépend exactement de la méthode à laquelle les appels sont effectués.
la source
Final signifie effectivement que votre classe pourra être modifiée à l'avenir sans impact sur les classes basées sur l'héritage en aval (car il n'y en a pas), ou sur tout problème lié à la sécurité des threads de la classe (je pense qu'il existe des cas où le mot clé final sur un champ empêche certains threads à haute jinx).
Final signifie que vous êtes libre de changer le fonctionnement de votre classe sans que des changements de comportement involontaires ne se glissent dans le code d’autrui reposant sur le vôtre.
À titre d’exemple, j’écris une classe appelée HobbitKiller, ce qui est excellent, car tous les hobbits sont des fous et devraient probablement mourir. Grattez ça, ils ont tous définitivement besoin de mourir.
Vous l'utilisez comme classe de base et ajoutez une nouvelle méthode impressionnante d'utilisation du lance-flammes, mais vous utilisez ma classe comme base, car j'ai une excellente méthode pour cibler les hobbits (en plus d'être rapide, ils sont rapides), ce qui vous utilisez pour aider à viser votre lance-flammes.
Trois mois plus tard, je change d'implémentation de ma méthode de ciblage. Désormais, à un moment ultérieur de votre mise à niveau de votre bibliothèque, l'implémentation d'exécution réelle de votre classe a été fondamentalement modifiée à cause d'une modification de la méthode de la super classe dont vous dépendez (et que vous ne contrôlez généralement pas).
Donc, pour que je sois un développeur consciencieux et que je veille à la disparition sans heurts des hobbits en utilisant ma classe, je dois faire très attention aux modifications que je fais aux classes qui peuvent être étendues.
En supprimant la possibilité d’agrandir, sauf dans les cas où j’ai précisément l’intention d’allonger la classe, je sauve beaucoup de maux de tête (ainsi que d’autres).
la source
final
L'utilisation de
final
n'est en aucun cas une violation des principes SOLID. Il est malheureusement extrêmement courant d’interpréter le principe Open / Closed ("les entités logicielles doivent être ouvertes à l’extension mais fermées à la modification") comme signifiant "plutôt que de modifier une classe, de la sous-classer et d’ajouter de nouvelles fonctionnalités". Ce n’est pas ce qu’on voulait au départ, et on considère généralement que ce n’est pas la meilleure approche pour atteindre ses objectifs.Le meilleur moyen de se conformer à OCP est de concevoir des points d’extension dans une classe, en fournissant spécifiquement des comportements abstraits paramétrés en injectant une dépendance à l’objet (par exemple, à l’aide du modèle de conception Stratégie). Ces comportements doivent être conçus pour utiliser une interface afin que les nouvelles implémentations ne reposent pas sur l'héritage.
Une autre approche consiste à implémenter votre classe avec son API publique en tant que classe abstraite (ou interface). Vous pouvez ensuite produire une implémentation entièrement nouvelle pouvant être connectée aux mêmes clients. Si votre nouvelle interface nécessite un comportement globalement similaire à celui de l'original, vous pouvez soit:
la source
Pour moi, c'est une question de design.
Supposons que j'ai un programme qui calcule les salaires des employés. Si j'ai une classe qui renvoie le nombre de jours ouvrables entre 2 dates en fonction du pays (une classe pour chaque pays), je mettrai cette dernière et fournirai une méthode à chaque entreprise pour que chaque entreprise dispose d'un jour de congé gratuit.
Pourquoi? Facile. Supposons qu'un développeur veuille hériter de la classe de base WorkingDaysUSA d'une classe WorkingDaysUSAmyCompany et le modifie pour refléter le fait que son entreprise sera fermée pour grève / maintenance / quelle que soit la raison le 2 mars.
Les calculs pour les commandes clients et la livraison reflèteront le retard et fonctionneront en conséquence lorsqu'ils appelleront WorkingDaysUSAmyCompany.getWorkingDays (), mais que se passera-t-il lorsque je calculerai le temps des vacances? Devrais-je ajouter le 2 mars comme jour férié pour tout le monde? Non, mais comme le programmeur a utilisé l'héritage et que je n'ai pas protégé la classe, cela peut prêter à confusion.
Ou bien, disons qu'ils héritent et modifient la classe pour refléter le fait que cette entreprise ne travaille pas le samedi, alors que dans le pays, ils travaillent à mi-temps le samedi. Puis un tremblement de terre, une crise de l’électricité ou une circonstance quelconque oblige le président à déclarer trois jours non ouvrables comme il s’est passé récemment au Venezuela. Si la méthode de la classe héritée est déjà soustraite chaque samedi, mes modifications sur la classe d'origine pourraient conduire à soustraire deux fois le même jour. Je devrais aller à chaque sous-classe sur chaque client et vérifier toutes les modifications sont compatibles.
Solution? Rendez la classe finale et fournissez une méthode addFreeDay (companyID mycompany, Date freeDay). De cette façon, vous êtes certain que lorsque vous appelez une classe WorkingDaysCountry, c'est votre classe principale et non une sous-classe.
la source