Je crois que la vraie raison pour laquelle beaucoup de gens pensent que les classes devraient être final
/ sealed
est que la plupart des classes extensibles non abstraites ne sont pas correctement documentées .
Permettez-moi de développer. Partant de loin, certains programmeurs estiment que l'héritage en tant qu'outil dans la POO est largement surutilisé et abusé. Nous avons tous lu le principe de substitution de Liskov, bien que cela ne nous ait pas empêchés de le violer des centaines (voire des milliers) de fois.
Le fait est que les programmeurs aiment réutiliser le code. Même quand ce n'est pas une si bonne idée. Et l'héritage est un outil clé dans cette «réutilisation» du code. Retour à la question finale / scellée.
La documentation appropriée pour une classe finale / scellée est relativement petite: vous décrivez ce que fait chaque méthode, quels sont les arguments, la valeur de retour, etc. Toutes les choses habituelles.
Cependant, lorsque vous documentez correctement une classe extensible, vous devez inclure au moins les éléments suivants :
- Dépendances entre les méthodes (quelle méthode appelle quoi, etc.)
- Dépendances des variables locales
- Contrats internes que la classe d'extension devrait honorer
- Convention d'appel pour chaque méthode (par exemple, lorsque vous la remplacez, appelez-vous l'
super
implémentation? L'appelez-vous au début de la méthode ou à la fin? Pensez constructeur vs destructeur)
- ...
Ce sont juste au-dessus de ma tête. Et je peux vous donner un exemple de la raison pour laquelle chacun de ces éléments est important et le sauter entraînera une extension de la classe.
Maintenant, considérez combien d'efforts de documentation devraient être nécessaires pour documenter correctement chacune de ces choses. Je crois qu'une classe avec 7-8 méthodes (ce qui peut être trop dans un monde idéalisé, mais trop peu dans le réel) pourrait aussi bien avoir une documentation d'environ 5 pages, texte seulement. Donc, à la place, nous nous retirons à mi-chemin et ne scellons pas la classe, afin que d'autres personnes puissent l'utiliser, mais ne la documentent pas non plus, car cela prendra un temps gigantesque (et, vous savez, cela pourrait ne jamais être prolongé de toute façon, alors pourquoi s'embêter?).
Si vous concevez une classe, vous pourriez ressentir la tentation de la sceller afin que les gens ne puissent pas l'utiliser d'une manière que vous n'avez pas prévue (et préparée). D'un autre côté, lorsque vous utilisez le code de quelqu'un d'autre, parfois à partir de l'API publique, il n'y a aucune raison visible pour que la classe soit finale, et vous pourriez penser "Merde, cela m'a juste coûté 30 minutes à la recherche d'une solution de contournement".
Je pense que certains des éléments d'une solution sont:
- D'abord, assurez-vous que l' extension est une bonne idée lorsque vous êtes client du code, et privilégiez vraiment la composition plutôt que l'héritage.
- Ensuite, lisez le manuel dans son intégralité (encore une fois en tant que client) pour vous assurer que vous ne négligez pas quelque chose qui est mentionné.
- Troisièmement, lorsque vous écrivez un morceau de code que le client utilisera, rédigez une documentation appropriée pour le code (oui, à long terme). À titre d'exemple positif, je peux donner les documents iOS d'Apple. Ils ne sont pas suffisants pour que l'utilisateur étende toujours correctement ses classes, mais ils incluent au moins quelques informations sur l'héritage. C'est plus que ce que je peux dire pour la plupart des API.
- Quatrièmement, essayez en fait d'étendre votre propre classe pour vous assurer que cela fonctionne. Je suis un grand partisan de l'inclusion de nombreux échantillons et tests dans les API, et lorsque vous faites un test, vous pouvez aussi bien tester les chaînes d'héritage: elles font partie de votre contrat après tout!
- Cinquièmement, dans les situations où vous avez un doute, indiquez que la classe n'est pas destinée à être prolongée et que le faire est une mauvaise idée (tm). Indiquez que vous ne devriez pas être tenu pour responsable d'une telle utilisation non intentionnelle, mais ne scellez pas la classe. Évidemment, cela ne couvre pas les cas où la classe doit être scellée à 100%.
- Enfin, lorsque vous scellez une classe, fournissez une interface en tant que hook intermédiaire, afin que le client puisse «réécrire» sa propre version modifiée de la classe et contourner votre classe «scellée». De cette façon, il peut remplacer la classe scellée par son implémentation. Maintenant, cela devrait être évident, car il s'agit d'un couplage lâche dans sa forme la plus simple, mais cela vaut toujours la peine d'être mentionné.
Il convient également de mentionner la question "philosophique" suivante: est-ce qu'une classe fait sealed
/ fait final
partie du contrat pour la classe, ou un détail d'implémentation? Maintenant, je ne veux pas y aller, mais une réponse à cela devrait également influencer votre décision de sceller ou non une classe.
Open/Closed principle
, pas leClosed Principle.