La visibilité private
des champs/propriétés/attributs de classe est-elle utile? Dans la POO, tôt ou tard, vous allez créer une sous-classe d'une classe et dans ce cas, il est bon de comprendre et de pouvoir modifier complètement l'implémentation.
L'une des premières choses que je fais lorsque je sous-classe une classe est de changer un tas de méthodes private
en protected
. Cependant, cacher les détails du monde extérieur est important - nous avons donc besoin de protected
et pas seulement public
.
Ma question est la suivante: connaissez-vous un cas d'utilisation important dans lequel private
au lieu de protected
est un bon outil, ou deux options "protected
& public
"suffira pour OOP langues?
Parce que comme vous le dites, protected
vous laisse toujours la possibilité de "modifier complètement l'implémentation". Il ne protège vraiment rien à l'intérieur de la classe.
Pourquoi nous soucions-nous de "protéger véritablement" les choses à l'intérieur de la classe? Parce que sinon il serait impossible de changer les détails d'implémentation sans casser le code client. Autrement dit, les personnes qui écrivent des sous-classes sont également "le monde extérieur" pour la personne qui a écrit la classe de base d'origine.
En pratique, les membres protected
sont essentiellement une "API publique pour les sous-classes" de classe et doivent rester stables et rétrocompatibles autant que les membres public
. Si nous n'avions pas la possibilité de créer de vrais membres private
, alors rien dans une implémentation ne serait jamais sûr de changer, car vous ne serait pas en mesure d'exclure la possibilité que le code client (non malveillant) ait en quelque sorte réussi à en dépendre.
Soit dit en passant, alors que "Dans la POO, tôt ou tard, vous allez faire une sous-classe d'une classe" est techniquement vrai, votre argument semble faire l'hypothèse beaucoup plus forte que "tôt ou tard, vous allez faire une sous-classe de chaque classe "ce qui n'est presque certainement pas le cas.
Dans la POO, tôt ou tard, vous allez créer une sous-classe d'une classe
C'est faux. Toutes les classes ne sont pas censées être sous-classées et certaines langues typées statiquement OOP ont même des fonctionnalités pour l'empêcher, par exemple, final
(Java et C++) ou sealed
(C #).
il est bon de comprendre et de pouvoir modifier complètement l'implémentation.
Non ce n'est pas. C'est bien pour une classe de pouvoir définir clairement son interface publique et de conserver ses invariants même si elle est héritée de.
En général, le contrôle d'accès concerne la compartimentation. Vous voulez qu'une partie individuelle du code soit comprise sans avoir à comprendre en détail comment elle interagit avec le reste du code. L'accès privé le permet. Si tout est au moins protégé, vous devez comprendre ce que fait chaque sous-classe afin de comprendre comment fonctionne la classe de base.
Ou pour le dire en termes de Scott Meyers: les parties privées d'une classe sont affectées par une quantité finie de code: le code de la classe elle-même.
Les parties publiques sont potentiellement affectées par chaque bit de code existant et chaque bit de code restant à écrire, ce qui est une quantité infinie de code.
Les parties protégées sont potentiellement affectées par chaque sous-classe existante et chaque sous-classe qui reste à écrire, ce qui représente également une quantité infinie de code.
La conclusion est que protégé vous donne très peu plus que public, alors que privé vous donne une réelle amélioration. C'est l'existence du spécificateur d'accès protégé qui est discutable, et non privée.
Oui, les champs privés sont absolument nécessaires. Cette semaine, j'avais besoin d'écrire une implémentation de dictionnaire personnalisée où je contrôlais ce qui était placé dans le dictionnaire. Si le champ du dictionnaire devait être rendu protégé ou public, alors les contrôles que j'avais si soigneusement écrits auraient pu être facilement contournés.
Les champs privés visent généralement à fournir des garanties que les données sont conformes au codeur d'origine. Rendez tout protégé/public et vous conduisez un entraîneur et des chevaux à travers ces procédures et validation.
Lorsque vous essayez de raisonner formellement sur l'exactitude d'un programme orienté objet il est typique d'utiliser une approche modulaire impliquant invariants d'objet . Dans cette approche
Le raisonnement modulaire sur un objet se déroule comme suit (au moins une première approximation)
Imaginez que nous vérifions un object A
en utilisant l'approche ci-dessus. Et je souhaite maintenant vérifier method g
de object B
qui appelle method f
de object A
. Le raisonnement modulaire nous permet de raisonner sur method g
sans avoir à reconsidérer la mise en œuvre de method f
. Pourvu qu'on puisse établir l'invariant de object A
et condition préalable de method f
sur le site de l'appel dans method g,
nous pouvons prendre la condition de post de method f
comme résumé du comportement de l'appel de méthode. De plus, nous saurons également qu'après le retour de l'appel, l'invariant de A
est toujours valable.
Cette modularité du raisonnement nous permet de penser formellement aux grands programmes. Nous pouvons raisonner sur chacune des méthodes individuellement, puis composer les résultats de ce raisonnement tour à tour pour raisonner sur de plus grandes parties du programme.
Les champs privés sont très utiles dans ce processus. Afin de savoir que l'invariant d'un objet continue de tenir entre deux appels de méthode sur cet objet, nous nous appuyons généralement sur le fait que l'objet n'est pas modifié dans l'intervalle.
Pour que le raisonnement modulaire fonctionne dans un contexte où les objets n'ont pas de champs privés, nous devons alors avoir un moyen de garantir que, quel que soit le champ défini par un autre objet, l'invariant soit toujours rétabli (après le champ ensemble). Il est difficile d'imaginer un invariant d'objet qui détient à la fois quelle que soit la valeur des champs de l'objet, et est également utile pour raisonner sur l'exactitude du programme. Il faudrait probablement inventer une convention compliquée autour de l'accès aux champs. Et probablement aussi perdre une partie (au pire même la totalité) de notre capacité à raisonner de manière modulaire.
Champs protégés
Les champs protégés restaurent une partie de notre capacité à raisonner de manière modulaire. Selon la langue, protected
peut restreindre la possibilité de définir un champ pour toutes les sous-classes ou toutes les sous-classes et classes de même package. Il arrive souvent que nous n'ayons pas accès à toutes les sous-classes lorsque nous raisonnons sur l'exactitude d'un objet que nous écrivons. Par exemple, vous pourriez écrire un composant ou une bibliothèque qui sera plus tard utilisé dans un programme plus grand (ou plusieurs programmes plus grands) - dont certains n'ont peut-être même pas encore été écrits. En règle générale, vous ne saurez pas si et de quelle manière il peut être sous-classé.
Cependant, il incombe généralement à une sous-classe de maintenir invariant l'objet de la classe qu'elle étend. Donc, dans un langage où protéger signifie "sous-classe" uniquement, et où nous sommes disciplinés pour garantir que les sous-classes maintiennent toujours les invariants de leur superclasse, vous pourriez faire valoir que le choix d'utiliser protégé au lieu de privé ne perd qu'une modularité minimale .
Bien que je parle de raisonnement formel, c'est souvent pensé que lorsque les programmeurs raisonnent de manière informelle sur l'exactitude de leur code, ils s'appuient parfois sur des types d'arguments similaires.
private
les variables d'une classe sont meilleures que protected
pour la même raison qu'une instruction break
à l'intérieur d'un bloc switch
est meilleure qu'un goto label
instruction; ce qui est que les programmeurs humains sont sujets aux erreurs.
Les variables protected
se prêtent à des abus non intentionnels (erreurs de programmation), tout comme l'instruction goto
se prête à la création de code spaghetti.
Est-il possible d'écrire du code sans bug en utilisant les variables de classe protected
? Oui bien sûr! Tout comme il est possible d'écrire du code sans bug en utilisant goto
; mais selon le cliché "Ce n'est pas parce que vous le pouvez!"
Les classes, et en effet le paradigme OO, existent pour se prémunir contre les malheureux programmeurs humains sujets aux erreurs qui font des erreurs. La défense contre les erreurs humaines est seulement aussi bonne que les mesures défensives intégrées dans la classe. l'implémentation de votre classe protected
est l'équivalent de souffler un énorme trou dans les murs d'une forteresse.
Les classes de base n'ont absolument aucune connaissance des classes dérivées. En ce qui concerne une classe de base, protected
ne vous offre pas plus de protection que public
, car rien n'empêche une classe dérivée de créer un public
getter/setter qui se comporte comme une porte dérobée.
Si une classe de base permet un accès sans entrave à ses détails d'implémentation internes, il devient alors impossible pour la classe elle-même de se défendre contre les erreurs. Les classes de base n'ont absolument aucune connaissance de leurs classes dérivées et n'ont donc aucun moyen de se prémunir contre les erreurs commises dans ces classes dérivées.
La meilleure chose qu'une classe de base puisse faire est de masquer autant que possible son implémentation que private
et de mettre en place suffisamment de restrictions pour éviter de casser les modifications des classes dérivées ou de toute autre chose en dehors de la classe.
En fin de compte, des langages de haut niveau existent pour minimiser les erreurs humaines. De bonnes pratiques de programmation (telles que principes SOLID ) existent également pour minimiser les erreurs humaines.
Les développeurs de logiciels qui ignorent les bonnes pratiques de programmation ont un risque d'échec beaucoup plus élevé et sont plus susceptibles de produire des solutions non maintenables cassées. Ceux qui suivent les bonnes pratiques ont un risque d'échec beaucoup plus faible et sont plus susceptibles de produire des solutions durables et fonctionnelles.
Les classes héritables ont deux contrats - l'un avec les détenteurs de références d'objet et les classes dérivées. Les membres publics sont liés par le contrat avec les titulaires de référence, et les membres protégés sont liés par le contrat avec les classes dérivées.
La création de membres protected
en fait une classe de base plus polyvalente, mais limite souvent la manière dont les futures versions de la classe pourraient changer. La création de membres private
permet à l'auteur de la classe d'avoir plus de souplesse pour modifier le fonctionnement interne de la classe, mais limite les types de classes qui peuvent en être utilement dérivés.
Par exemple, List<T>
dans .NET rend le magasin de sauvegarde privé; s'il était protégé, les types dérivés pourraient faire des choses utiles qui ne seraient pas possibles autrement, mais les futures versions de List<T>
devrait toujours utiliser son magasin de sauvegarde monolithique maladroit, même pour des listes contenant des millions d'articles. Rendre le magasin de sauvegarde privé permettrait les futures versions de List<T>
pour utiliser un magasin de sauvegarde plus efficace sans casser les classes dérivées.
Je pense qu'il y a une hypothèse clé dans votre argument selon lequel quand quelqu'un écrit une classe, il ne sait pas qui pourrait étendre cette classe sur la route et pour quelle raison . Compte tenu de cette hypothèse, votre argument serait parfaitement logique, car chaque variable que vous rendez privée pourrait potentiellement couper une voie de développement sur la route. Cependant, je rejetterais cette hypothèse.
Si cette hypothèse est rejetée, alors il n'y a que deux cas à considérer.
Dans ce cas, l'auteur sait que quelqu'un étendra la classe et pourquoi et saura donc exactement quoi protéger et protéger. Ils utilisent la distinction privé/protégé pour communiquer une sorte d'interface à l'utilisateur créant la sous-classe.
Ce cas devrait être rare (vous pourriez dire qu'il n'est pas légitime) et n'est pas préféré à la simple modification de la classe d'origine dans la base de code d'origine. Cela pourrait également être un symptôme d'une mauvaise conception. Dans ces cas, je préférerais que la personne piratant le comportement utilise simplement d'autres hacks comme des amis (C/C++) et setAccessible(true)
(Java).
Je pense qu'il est sûr de rejeter cette hypothèse.
Cela revient généralement à l'idée de composition sur héritage . L'héritage est souvent enseigné comme un moyen idéal pour réduire la réutilisation du code, mais il devrait rarement être le premier choix pour la réutilisation du code. Je n'ai pas un simple argument de renversement et cela peut être assez difficile et litigieux à comprendre. Cependant, dans mon expérience avec la modélisation de domaine, j'ai constaté que j'utilise rarement l'héritage sans avoir une compréhension très claire de qui héritera de ma classe et pourquoi.
Les trois niveaux d'accès ont leur cas d'utilisation, OOP serait incomplet sans aucun d'entre eux. Habituellement, vous le faites
Et vous ne vous écartez de ce schéma général que s'il y a un bonne raison ™. Méfiez-vous de "cela me facilitera la vie lorsque je pourrai y accéder librement de l'extérieur" (et de l'extérieur ici inclut également des sous-classes). Lorsque j'implémente des hiérarchies de classes, je commence souvent par des classes qui n'ont pas de membres protégés, jusqu'à ce que j'arrive à les sous-classer/les étendre/les spécialiser, devenant les classes de base d'un framework/toolkit et parfois déplaçant une partie de leurs fonctionnalités d'origine d'un niveau vers le haut.
Beaucoup de bonnes réponses ici, mais je vais quand même apporter mes deux cents. :-)
Privé est bon pour la même raison que les données globales sont mauvaises.
Si une classe déclare des données privées, alors vous savez absolument que le seul code qui dérange avec ces données est le code de la classe. Quand il y a un bug, vous n'avez pas à chercher partout dans la création pour trouver chaque endroit qui pourrait changer ces données. Tu sais que c'est dans la classe. Lorsque vous apportez une modification au code et que vous modifiez quelque chose sur la façon dont ce champ est utilisé, vous n'avez pas besoin de rechercher tous les emplacements potentiels qui pourraient utiliser ce champ et d'étudier si votre modification planifiée les cassera. Vous savez que les seuls endroits sont à l'intérieur de la classe.
J'ai eu beaucoup, beaucoup de fois que j'ai dû apporter des modifications aux classes qui sont dans une bibliothèque et utilisées par plusieurs applications, et je dois faire très attention pour ne pas casser une application dont je ne sais rien. Plus il y a de données publiques et protégées, plus il y a de risques de problèmes.
Je pense qu'il vaut la peine de mentionner quelques opinions dissidentes.
En théorie, il est bon d'avoir un niveau d'accès contrôlé pour toutes les raisons mentionnées dans d'autres réponses.
Dans la pratique, trop souvent lors de la révision de code, je vois des gens (qui aiment utiliser privé), changer le niveau d'accès de privé -> protégé et pas trop souvent de protégé -> public. Presque toujours, la modification des propriétés de classe implique la modification de setters/getters. Cela m'a fait perdre beaucoup de temps (révision de code) et le leur (changement de code).
Cela m'énerve également que cela signifie que leurs classes ne sont pas fermées pour modification.
C'était avec du code interne où vous pouvez toujours le changer si vous en avez besoin aussi. La situation est pire avec un code tiers lorsqu'il n'est pas si facile de changer de code.
Alors, combien de programmeurs pensent que c'est un problème? Eh bien, combien utilisent des langages de programmation qui n'ont pas de privé? Bien sûr, les gens n'utilisent pas seulement ces langues parce qu'ils n'ont pas de spécificateurs privés, mais cela aide à simplifier les langues et la simplicité est importante.
Imo, c'est très similaire au typage dynamique/statique. En théorie, le typage statique est très bon. En pratique, cela n'empêche que 2% des erreurs L'efficacité déraisonnable du typage dynamique ... . L'utilisation de private empêche probablement moins d'erreur que cela.
Je pense que les principes SOLID sont bons, je souhaite que les gens se soucient d'eux plus qu'ils ne se soucient de créer une classe publique, protégée et privée.
Une question plus intéressante, peut-être, est de savoir pourquoi un autre type de domaine que privé est nécessaire. Lorsqu'une sous-classe doit interagir avec les données d'une superclasse, cela crée directement un couplage direct entre les deux, tandis que l'utilisation de méthodes pour prévoir l'interaction entre les deux permet un niveau d'indirection qui peut permettre d'apporter des modifications à la superclasse qui serait autrement très difficile.
Un certain nombre de langages (par exemple Ruby et Smalltalk) ne fournissent pas de champs publics afin que les développeurs ne soient pas encouragés à autoriser le couplage direct à leurs implémentations de classe, mais pourquoi ne pas aller plus loin et ne disposer que de champs privés? ne serait pas une perte de généralité (car la superclasse peut toujours fournir des accesseurs protégés pour la sous-classe), mais cela garantirait que les classes ont toujours au moins un petit degré d'isolement de leurs sous-classes. Pourquoi n'est-ce pas une conception plus courante?
Les méthodes/variables privées seront généralement cachées dans une sous-classe. Ça peut être une bonne chose.
Une méthode privée peut faire des hypothèses sur les paramètres et laisser la vérification de l'intégrité à l'appelant.
Une méthode protégée devrait entrées de vérification d'intégrité.
Je voudrais également ajouter un autre exemple pratique expliquant pourquoi protected
ne suffit pas. Dans mon université, les premières années entreprennent un projet où ils doivent développer une version de bureau d'un jeu de société (pour que plus tard une IA soit développée et connectée à d'autres joueurs via un réseau). Un code partiel leur est fourni pour les démarrer, y compris un cadre de test. Certaines des propriétés de la classe de jeu principale sont exposées sous la forme protected
afin que les classes de test qui étendent cette classe y aient accès. Mais ces champs ne sont pas des informations sensibles.
En tant qu'AT pour l'unité, je vois souvent des étudiants faire simplement tout leur code ajouté protected
ou public
(peut-être parce qu'ils ont vu les autres choses protected
et public
et supposé qu'ils devraient emboîter le pas). Je leur demande pourquoi leur niveau de protection est inapproprié et beaucoup ne savent pas pourquoi. La réponse est que les informations sensibles qu'ils exposent aux sous-classes signifient qu'un autre joueur peut tricher en étendant simplement cette classe et en accédant aux informations hautement sensibles du jeu (essentiellement l'emplacement caché de l'adversaire, je suppose que ce serait le cas si vous pourrait voir vos pièces adverses sur un plateau de cuirassés en prolongeant une classe). Cela rend leur code très dangereux dans le contexte du jeu.
En dehors de cela, il existe de nombreuses autres raisons de garder quelque chose de privé, même pour vos sous-classes. Cela pourrait être de cacher des détails d'implémentation qui pourraient gâcher le bon fonctionnement de la classe s'ils sont modifiés par quelqu'un qui ne sait pas nécessairement ce qu'ils font (en pensant principalement à d'autres personnes utilisant votre code ici).