web-dev-qa-db-fra.com

Pourquoi avoir des champs privés, n'est pas suffisamment protégé?

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?

267
Adam Libuša

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.

227
Ixrec

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.

255
Sebastian Redl

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.

33
Robbie Dee

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

  1. Des méthodes leur sont associées avant et après les conditions (contrats).
  2. Les objets sont associés à des invariants.

Le raisonnement modulaire sur un objet se déroule comme suit (au moins une première approximation)

  1. Prouver que le constructeur de l'objet établit l'invariant
  2. Pour chaque méthode non privée, supposez que l'invariant de l'objet et la précondition de la méthode se maintiennent à l'entrée, puis prouvez que le corps du code implique que la postcondition et l'invariant se maintiennent à la sortie de la méthode

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.

12
flamingpenguin

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.

8
Ben Cottrell

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.

4
supercat

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.

  1. L'auteur de la classe d'origine avait des idées très claires sur les raisons pour lesquelles il pourrait être étendu (par exemple, il s'agit d'un BaseFoo et il y aura plusieurs implémentations concrètes de Foo en cours de route).

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.

  1. L'auteur de la classe enfant essaie de pirater un comportement dans une classe parent.

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.

4
Pace

Les trois niveaux d'accès ont leur cas d'utilisation, OOP serait incomplet sans aucun d'entre eux. Habituellement, vous le faites

  • faire toutes les variables/membres de données privé. Vous ne voulez pas que quelqu'un de l'extérieur gâche vos données internes. Aussi des méthodes qui fournissent des fonctionnalités auxiliaires (pensez à des calculs basés sur plusieurs variables membres) à votre public ou protégé interface - c'est uniquement pour un usage interne, et vous voudrez peut-être le changer/l'améliorer à l'avenir.
  • faire l'interface générale de votre classe public. C'est à cela que les utilisateurs de votre classe d'origine sont censés travailler, et à quoi vous pensez que les classes dérivées devraient également ressembler. Afin de fournir une encapsulation appropriée, ce ne sont généralement que des méthodes (et des classes/structures d'assistance, des énumérations, des typedefs, tout ce dont l'utilisateur a besoin pour travailler avec vos méthodes), pas des variables.
  • déclarer les méthodes protégées qui pourraient être utiles à quelqu'un qui souhaite étendre/spécialiser les fonctionnalités de votre classe, mais ne devraient pas faire partie du public interface - en fait, vous élevez généralement des membres privés pour les protéger quand c'est nécessaire. En cas de doute, ce n'est pas le cas, jusqu'à ce que vous sachiez que
    1. votre classe peut/peut/sera sous-classée,
    2. et avoir une idée claire de ce que peuvent être les cas d'utilisation du sous-classement.

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.

2
Murphy

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.

1
Jay

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.

1
imel96

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?

1
Jules

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é.

0
Phil Lello

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).

0
J_mie6