Cette question de savoir quand utiliser privé et quand utiliser protégé en classe m'a fait réfléchir. (Je vais également étendre cette question aux classes et méthodes finales, car elles sont liées. Je programme en Java, mais je pense que cela s'applique à tous les langages POO)
Une bonne règle est de rendre tout aussi privé que possible.
- Rendez toutes les classes finales, sauf si vous devez les sous-classer immédiatement.
- Définissez toutes les méthodes comme définitives, sauf si vous devez les sous-classer et les remplacer immédiatement.
- Rendez tous les paramètres de méthode définitifs, sauf si vous devez les modifier dans le corps de la méthode, ce qui est un peu gênant la plupart du temps de toute façon.
C'est assez simple et clair, mais que faire si j'écris principalement des bibliothèques (Open Source sur GitHub) au lieu d'applications?
Je pourrais nommer beaucoup de bibliothèques et de situations, où
- Une bibliothèque s'est agrandie d'une manière à laquelle les développeurs n'auraient jamais pensé
- Cela devait être fait avec "class loader magic" et d'autres hacks à cause des contraintes de visibilité
- Les bibliothèques ont été utilisées d'une manière pour laquelle elles n'étaient pas conçues et la fonctionnalité requise a été "piratée" dans
- Les bibliothèques n'ont pas pu être utilisées en raison d'un petit problème (bogue, fonctionnalité manquante, comportement "incorrect") qui n'a pas pu être modifié en raison d'une visibilité réduite
- Un problème qui n'a pas pu être résolu a conduit à des solutions de contournement énormes, laides et boguées où le remplacement d'une fonction simple (qui était privée ou finale) aurait pu aider
Et j'ai commencé à les nommer jusqu'à ce que la question devienne trop longue et j'ai décidé de les supprimer.
J'aime l'idée de ne pas avoir plus de code que nécessaire, plus de visibilité que nécessaire, plus d'abstraction que nécessaire. Et cela pourrait fonctionner lors de l'écriture d'une application pour l'utilisateur final, où le code n'est utilisé que par ceux qui l'écrivent. Mais comment cela peut-il tenir si le code est destiné à être utilisé par d'autres développeurs, où il est improbable que le développeur d'origine ait pensé à tous les cas d'utilisation possibles à l'avance et que les modifications / refactors soient difficiles / impossibles à faire?
Étant donné que les grandes bibliothèques open source ne sont pas une nouveauté, quelle est la façon la plus courante de gérer la visibilité dans de tels projets avec des langages orientés objet?
Réponses:
La triste vérité est que de nombreuses bibliothèques sont écrites et non conçues . C'est triste, car un peu de réflexion préalable peut éviter beaucoup de problèmes sur la route.
Si nous décidons de concevoir une bibliothèque, il y aura un ensemble de cas d'utilisation prévus. La bibliothèque peut ne pas satisfaire directement tous les cas d'utilisation, mais peut faire partie d'une solution. La bibliothèque doit donc être suffisamment flexible pour s'adapter.
La contrainte est que ce n'est généralement pas une bonne idée de prendre le code source de la bibliothèque et de le modifier pour gérer le nouveau cas d'utilisation. Pour les bibliothèques propriétaires, la source peut ne pas être disponible et pour les bibliothèques open source, il peut être indésirable de conserver une version fourchue. Il peut ne pas être possible de fusionner des adaptations très spécifiques dans le projet en amont.
C'est là qu'intervient le principe ouvert-fermé: la bibliothèque doit être ouverte à l'extension sans modifier le code source. Cela ne vient pas naturellement. Cela doit être un objectif de conception intentionnel. Il existe une multitude de techniques qui peuvent vous aider ici, les modèles de conception OOP classiques en sont certains. En général, nous spécifions des hooks où le code utilisateur peut se connecter en toute sécurité à la bibliothèque et ajouter des fonctionnalités.
Il ne suffit pas de rendre chaque méthode publique ou de permettre à chaque classe d'être sous-classée pour atteindre l'extensibilité. Tout d'abord, il est vraiment difficile d'étendre la bibliothèque si l'on ne sait pas où l'utilisateur peut se connecter à la bibliothèque. Par exemple, remplacer la plupart des méthodes n'est pas sûr car la méthode de la classe de base a été écrite avec des hypothèses implicites. Vous avez vraiment besoin de concevoir pour l'extensibilité.
Plus important encore, une fois que quelque chose fait partie de l'API publique, vous ne pouvez pas le reprendre. Vous ne pouvez pas le refactoriser sans casser le code en aval. Une ouverture prématurée limite la bibliothèque à une conception sous-optimale. En revanche, rendre les éléments internes privés mais ajouter des crochets si nécessaire, est une approche plus sûre. Bien que ce soit une manière saine d'aborder l'évolution à long terme d'une bibliothèque, cela n'est pas satisfaisant pour les utilisateurs qui ont besoin d'utiliser la bibliothèque en ce moment .
Alors que se passe-t-il à la place? En cas de problème important avec l'état actuel de la bibliothèque, les développeurs peuvent prendre toutes les connaissances sur les cas d'utilisation réels accumulés au fil du temps et écrire une version 2 de la bibliothèque. Ce sera génial! Il corrigera tous ces bogues de conception! Cela prendra également plus de temps que prévu, dans de nombreux cas, il disparaîtra. Et si la nouvelle version est très différente de l'ancienne version, il peut être difficile d'encourager les utilisateurs à migrer. Il vous reste alors à conserver deux versions incompatibles.
la source
Chaque classe / méthode publique et extensible fait partie de votre API qui doit être prise en charge. Limiter cet ensemble à un sous-ensemble raisonnable de la bibliothèque permet la plus grande stabilité et limite le nombre de choses qui peuvent mal tourner. C'est une décision de gestion (et même les projets OSS sont gérés dans une certaine mesure) en fonction de ce que vous pouvez raisonnablement soutenir.
La différence entre OSS et source fermée est que la plupart des gens essaient de créer et de développer une communauté autour du code afin que ce soit plus d'une personne qui gère la bibliothèque. Cela dit, plusieurs outils de gestion sont disponibles:
Dans les projets matures, ce que vous verrez est quelque chose du genre:
À ce stade, si la modification a été acceptée mais que l'utilisateur souhaite accélérer sa correction, il peut effectuer le travail et soumettre une demande d'extraction ou un correctif (selon l'outil de contrôle de version).
Aucune API n'est statique. Cependant, sa croissance doit être façonnée d'une manière ou d'une autre. En gardant tout fermé jusqu'à ce qu'il y ait un besoin avéré d'ouvrir les choses, vous évitez d'avoir la réputation d'un buggy ou d'une bibliothèque instable.
la source
Je vais reformuler ma réponse car il semble qu'elle ait frappé un nerf avec quelques personnes.
La visibilité de la propriété / méthode de classe n'a rien à voir avec la sécurité ni l'ouverture des sources.
La visibilité existe parce que les objets sont fragiles à 4 problèmes spécifiques:
Si vous construisez votre module sans encapsulation, vos utilisateurs s'habitueront à modifier directement l'état du module. Cela fonctionne très bien dans un environnement à thread unique, mais une fois que vous pensez même à ajouter des threads; vous serez obligé de rendre l'État privé et d'utiliser des verrous / moniteurs avec des getters et des setters qui font que d'autres threads attendent les ressources, plutôt que de courir dessus. Cela signifie que vos programmes utilisateurs ne fonctionneront plus car les variables privées ne sont pas accessibles de manière conventionnelle. Cela peut signifier que vous avez besoin de beaucoup de réécritures.
La vérité est qu'il est beaucoup plus facile de coder avec un seul runtime threadé à l'esprit, et un mot-clé privé vous permet d'ajouter simplement le mot-clé synchronisé, ou quelques verrous, et le code de vos utilisateurs ne se cassera pas si vous l'encapsulez depuis le début .
Chaque objet a un tas de choses dont il a besoin pour être vrai afin d'être dans un état cohérent. Malheureusement, ces choses vivent dans l'espace visible du client car il est coûteux de déplacer chaque objet dans son propre processus et de lui parler via des messages. Cela signifie qu'il est très facile pour un objet de planter tout le programme si l'utilisateur a une visibilité complète.
Ceci est inévitable, mais vous pouvez éviter de mettre accidentellement un objet dans un état incohérent en fermant l'interface sur ses services qui empêchent les plantages accidentels en permettant uniquement à l'utilisateur d'interagir avec l'état de l'objet via une interface soigneusement conçue qui rend le programme beaucoup plus robuste. . Cela ne signifie pas que l'utilisateur ne peut pas corrompre intentionnellement les invariants, mais s'ils le font, c'est leur client qui se bloque, il leur suffit de redémarrer le programme (les données que vous souhaitez protéger ne doivent pas être stockées côté client ).
Un autre bel exemple où vous pouvez améliorer la convivialité de vos modules est de rendre le constructeur privé; car si le constructeur lève une exception, il tuera le programme. Une approche paresseuse pour résoudre ce problème est de faire en sorte que le constructeur vous envoie une erreur de temps de compilation que vous ne pouvez pas la construire à moins qu'elle ne soit dans un bloc try / catch. En rendant le constructeur privé et en ajoutant une méthode de création statique publique, vous pouvez demander à la méthode create de retourner null si elle ne parvient pas à le construire, ou de prendre une fonction de rappel pour gérer l'erreur, ce qui rend le programme plus convivial.
De nombreuses classes ont beaucoup d'états et de méthodes et il est facile de se laisser submerger en essayant de les parcourir; Beaucoup de ces méthodes ne sont que du bruit visuel comme les fonctions d'assistance, l'état. rendre les variables et les méthodes privées permet de réduire la pollution de la portée et de faciliter la recherche des services recherchés par l'utilisateur.
Essentiellement, cela vous permet de vous passer de fonctions d'assistance à l'intérieur de la classe plutôt qu'à l'extérieur de la classe; sans contrôle de la visibilité sans distraire l'utilisateur avec un tas de services que l'utilisateur ne devrait jamais utiliser, vous pouvez donc vous passer de décomposer les méthodes en un tas de méthodes d'assistance (même si cela polluera toujours votre portée, mais pas l'utilisateur).
Une interface bien conçue peut masquer ses bases de données internes / fenêtres / imagerie dont elle dépend pour faire son travail, et si vous souhaitez passer à une autre base de données / un autre système de fenêtrage / une autre bibliothèque d'imagerie, vous pouvez garder l'interface identique et celle des utilisateurs ne remarquera pas.
D'un autre côté, si vous ne le faites pas, vous pouvez facilement tomber dans l'impossibilité de modifier vos dépendances, car elles sont exposées et le code s'appuie dessus. Avec un système suffisamment grand, le coût de la migration peut devenir inabordable, tandis qu'un encapsulage peut protéger les utilisateurs clients qui se comportent bien des décisions futures de permuter les dépendances.
la source