Les attributs protégés doivent-ils toujours être interdits?
Question
J'utilise rarement l'héritage, mais quand je le fais, je n'utilise jamais d'attributs protégés car je pense que cela casse l'encapsulation des classes héritées.
Utilisez-vous des attributs protégés? à quoi les utilisez-vous?
La solution
Dans cette interview sur Design de Bill Venners, Joshua Bloch, l'auteur de < em> Effective Java dit:
Confiance des sous-classes
Bill Venners: Dois-je faire confiance aux sous-classes plus intimement que non-sous-classes? Par exemple, est-ce que je fais c'est plus facile pour une sous-classe la mise en œuvre de me casser que je serait pour un non-sous-classe? Dans en particulier, comment vous sentez-vous données protégées?
Josh Bloch: pour écrire quelque chose qui est à la fois sous-classable et robuste contre une sous-classe malveillante est en fait une chose assez difficile à faire, en supposant que vous donniez l'accès à la sous-classe à vos structures de données internes. Si la sous-classe n'a pas accès à tout ce qu'un utilisateur ordinaire pas, alors il est plus difficile pour le sous-classe à faire des dégâts. Mais à moins que vous rendez toutes vos méthodes finales, la sous-classe peut encore casser votre contrats en faisant juste le mauvais les choses en réponse à la méthode invocation. C'est précisément pourquoi le classes critiques de sécurité comme String sont finales. Sinon quelqu'un pourrait écrire une sous-classe qui fabrique des chaînes semble mutable, ce qui serait suffisant pour casser la sécurité. Donc vous doit faire confiance à vos sous-classes. Si vous ne leur faites pas confiance, alors vous ne pouvez pas permettre eux, car les sous-classes peuvent si facilement amener une classe à violer sa contrats.
En ce qui concerne les données protégées en général, c'est un mal nécessaire. CA devrait etre gardé au minimum. La plupart des données protégées et méthodes protégées équivalent à s'engager dans une implémentation détail. Un champ protégé est un détail de mise en œuvre que vous êtes rendre visible aux sous-classes. Même un méthode protégée est un morceau de structure interne que vous faites visible par les sous-classes.
La raison pour laquelle vous le rendez visible est que il est souvent nécessaire pour permettre sous-classes à faire leur travail, ou à faire c'est efficace. Mais une fois que vous avez terminé vous y êtes engagé. C'est maintenant quelque chose que vous n'êtes pas autorisé à changer, même si vous trouvez plus tard un mise en œuvre efficace que non plus implique l'utilisation d'un domaine ou méthode particulière.
Donc, toutes choses égales par ailleurs, vous ne devrait pas avoir de membres protégés du tout. Mais cela dit, si vous en avez aussi peu, votre classe peut ne pas être utilisable comme une super classe, ou du moins pas comme une super classe efficace. Souvent vous découvrez après le fait. Ma philosophie est d'avoir aussi peu de membres protégés que possible lorsque vous écrivez le classe. Ensuite, essayez de le sous-classer. Vous peut découvrir que sans un particulier méthode protégée, toutes les sous-classes faire quelque chose de mal.
Par exemple, si vous regardez
AbstractList
, vous constaterez qu'il existe est une méthode protégée pour supprimer un gamme de la liste en un seul coup (removeRange
). Pourquoi est-ce là-dedans? Parce que le langage normal pour enlever un gamme, basée sur l'API publique, est de appelezsubList
pour obtenir une sous-liste
, puis appelezclear
à ce sujet sous-liste
. Sans ce particulier méthode protégée, cependant, le seul Ce queclear
pourrait faire est supprimer à plusieurs reprises des éléments individuels.Pensez-y. Si vous avez un tableau représentation, que va-t-il faire? Il va à plusieurs reprises réduire le tableau, faire l'ordre n travail N fois. Alors ça va prendre une quantité de travail quadratique, au lieu de la quantité linéaire de travail qu'il le devrait. En fournissant cette méthode protégée, nous permettons à mise en œuvre qui peut efficacement supprimer une plage entière pour le faire. Et toute implémentation
List
raisonnable peut supprimer une plage plus efficacement tout à la fois.Que nous en aurions besoin la méthode est quelque chose que vous auriez à être bien plus intelligent que moi pour savoir de face. Fondamentalement, j'ai implémenté le chose. Puis, comme nous avons commencé à sous-classe nous avons réalisé que la plage était effacée quadratique. Nous ne pouvions pas nous permettre cela, alors Je mets dans la méthode protégée. je pense c'est la meilleure approche avec méthodes protégées. Mettre dans aussi peu que possible, puis ajoutez-en au besoin. Les méthodes protégées représentent engagements à des conceptions que vous pouvez vouloir changer. Vous pouvez toujours ajouter méthodes protégées, mais vous ne pouvez pas prendre les sortir.
Bill Venners: Et des données protégées?
Josh Bloch: La même chose, mais plus encore. Les données protégées, c'est encore plus dangereux en termes de gâcher votre invariants de données. Si vous donnez à quelqu'un sinon l'accès à certaines données internes, ils en ont le règne libre.
Version courte: il casse l’encapsulation, mais c’est un mal nécessaire qui doit être limité au minimum.
Autres conseils
C #:
J'utilise protected pour les méthodes abstraites ou virtuelles que je veux que les classes de base remplacent. Je crée également une méthode protégée si elle peut être appelée par des classes de base, mais je ne veux pas qu'elle soit appelée en dehors de la hiérarchie des classes.
Vous en aurez peut-être besoin pour l'attribut statique (ou "global") dont vous souhaitez que vos sous-classes ou classes du même package (s'il s'agit de Java).
Ces attributs finaux statiques représentant une sorte de "valeur constante" ont rarement une fonction de lecture, un attribut final statique protégé peut donc être utile dans ce cas.
Scott Meyers indique que ne n'utilisez pas d'attributs protégés dans Effective C ++ (3 e éd.):
Point 22: Déclarez les membres de données privés.
La raison est la même que vous donnez: cela casse les encapsulations. La conséquence est que sinon, des modifications locales dans la présentation de la classe pourraient rompre les types dépendants et entraîner des modifications à de nombreux autres endroits.
Il n'y a jamais de bonnes raisons d'avoir des attributs protégés. Une classe de base doit pouvoir dépendre de l'état, ce qui implique de restreindre l'accès aux données par le biais de méthodes d'accès. Vous ne pouvez donner à personne l'accès à vos données personnelles, même à vos enfants.
Le mot clé protected
est une erreur conceptuelle et une mauvaise conception des langues, ainsi que plusieurs langues modernes, telles que Nim et Ceylan (voir http://ceylon-lang.org/documentation/faq/language-design/#no_protected_modifier ), qui a été soigneusement conçu plutôt que copiez simplement les erreurs courantes, ne possédez pas un tel mot clé.
Ce ne sont pas les membres protégés qui rompent l’encapsulation, ils exposent les membres qui ne devraient pas être exposés mais qui cassent l’encapsulation ... peu importe qu’ils soient protégés ou publics. Le problème avec protected
est qu’il est erroné et trompeur ... déclarer des membres protected
(plutôt que privé
) ne les protège pas, il fait le contraire, exactement comme public
. Un membre protégé, accessible en dehors de la classe, est exposé au monde et sa sémantique doit donc être conservée pour toujours, comme dans le cas de public
. L’idée même de " protégé " est absurde ... l'encapsulation n'est pas une sécurité, et le mot clé ne fait que favoriser la confusion entre les deux. Vous pouvez aider un peu en évitant toute utilisation de protected
dans vos propres classes - si quelque chose fait partie interne de l'implémentation, ne fait pas partie de la sémantique de la classe et peut changer dans le futur, puis le rendre privé ou interne à votre paquet, module, assemblage, etc. S'il s'agit d'une partie non modifiable de la sémantique de la classe, alors rendez-le public, et vous n'ennuierez pas les utilisateurs de votre classe qui peuvent voir qu'il existe une utilité utile. membre dans la documentation, mais ne peut pas l'utiliser, à moins qu'ils ne créent leurs propres instances et qu'ils puissent y accéder en sous-classant.
Je n'utilise pas d'attributs protégés en Java, car ils ne sont protégés que par paquets. Mais en C ++, je les utiliserai dans des classes abstraites, ce qui permettra à la classe héritière de les hériter directement.
En général, non, vous ne voulez vraiment pas utiliser les membres de données protégés. Ceci est d'autant plus vrai si vous écrivez une API. Une fois que quelqu'un hérite de votre classe, vous ne pouvez jamais vraiment faire de maintenance et ne pas le casser de façon bizarre et parfois sauvage.
Je pense que les attributs protégés sont une mauvaise idée. J'utilise CheckStyle pour appliquer cette règle à mes équipes de développement Java.
J'ai récemment travaillé sur un projet qui était "protégé". Le député était une très bonne idée. La hiérarchie de classe était quelque chose comme:
[+] Base
|
+--[+] BaseMap
| |
| +--[+] Map
| |
| +--[+] HashMap
|
+--[+] // something else ?
La base a implémenté std :: list mais rien d’autre. L’accès direct à la liste était interdit à l’utilisateur, mais la classe de base étant incomplète, elle s’appuyait néanmoins sur les classes dérivées pour implémenter l’indirection vers la liste.
L'indirection peut provenir d'au moins deux types: std :: map et stdext :: hash_map. Les deux cartes se comporteront de la même manière, mais hash_map a besoin que la clé soit hashable (dans VC2003, convertible en taille_t).
BaseMap a donc implémenté un TMap en tant que type basé sur un modèle qui était un conteneur semblable à une carte.
Map et HashMap sont deux classes dérivées de BaseMap, l'une spécialisée BaseMap sur std :: map et l'autre sur stdext :: hash_map.
Donc:
-
La base n'était pas utilisable en tant que telle (aucun accesseur public!) et ne fournissait que des fonctionnalités et du code communs
-
BaseMap avait besoin de lire / écrire facilement dans un std :: list
-
Map et HashMap avaient besoin d’un accès facile en lecture / écriture au TMap défini dans BaseMap.
Pour moi, la seule solution consistait à utiliser protected pour les variables de membre std :: list et TMap. Il était hors de question que je mette ceux-ci "privés". car je voudrais de toute façon exposer toutes ou presque toutes leurs fonctionnalités via des accesseurs lecture / écriture.
En fin de compte, je suppose que si vous divisez votre classe en plusieurs objets, chaque dérivation ajoutant les fonctionnalités nécessaires à sa classe mère, et seule la classe la plus dérivée pouvant être réellement utilisée est protégée. Le fait que le " membre protégé " était une classe, et donc, il était presque impossible de "casser", aidé.
Mais sinon, vous devez éviter autant que possible les objets protégés (c'est-à-dire, utilisez private par défaut et public lorsque vous devez exposer la méthode).
Je les utilise. En bref, c’est un bon moyen de partager certains attributs. Certes, vous pouvez écrire des fonctions set / get pour eux, mais s’il n’ya pas de validation, quel est le but? C'est aussi plus rapide.
Considérez ceci: vous avez une classe qui est votre classe de base. Il a pas mal d'attributs que vous ne pouvez pas utiliser dans les objets enfants. Vous pouvez écrire une fonction get / set pour chacun ou simplement les définir.
Mon exemple typique est un gestionnaire de fichiers / flux. Vous souhaitez accéder au gestionnaire (descripteur de fichier), mais vous souhaitez le masquer aux autres classes. C'est beaucoup plus simple que d'écrire une fonction set / get pour elle.
En général, oui. Une méthode protégée est généralement préférable.
En utilisation, l'utilisation d'une variable finale protégée pour un objet partagé par tous les enfants d'une classe offre un niveau de simplicité donné. Je conseillerais toujours de ne pas l'utiliser avec des primitives ou des collections, car les contrats sont impossibles à définir pour ces types.
Dernièrement, je suis venu séparer ce que vous faites des primitives et des collections brutes de celui que vous faites des classes bien formées. Les primitives et les collections doivent TOUJOURS être privées.
De plus, j'ai commencé à exposer de temps en temps les variables de membre public lorsqu'elles sont déclairées comme étant définitives et constituent des classes bien formées qui ne sont pas trop flexibles (encore une fois, pas de primitives ou de collections).
Ce n’est pas un raccourci stupide, j’y ai pensé sérieusement et j’ai décidé qu’il n’y avait absolument aucune différence entre une variable publique finale exposant un objet et un getter.
Cela dépend de ce que vous voulez. Si vous voulez une classe rapide, les données doivent être protégées et utiliser des méthodes protégées et publiques. Parce que je pense que vous devriez supposer que vos utilisateurs issus de votre classe connaissent très bien votre classe ou au moins ils ont lu votre manuel au moment de leur utilisation.
Si vos utilisateurs dérangent votre classe, ce n’est pas votre problème. Tout utilisateur malveillant peut ajouter les lignes suivantes lors du remplacement d’un de vos virtuels:
(C #)
static Random rnd=new Random();
//...
if (rnd.Next()%1000==0) throw new Exception("My base class sucks! HAHAHAHA! xD");
//...
Vous ne pouvez pas sceller toutes les classes pour empêcher cela.
Bien sûr, si vous voulez une contrainte sur certains de vos champs, utilisez des propriétés ou des propriétés d'accesseur ou quelque chose que vous préférez, et rendez ce champ privé car il n'y a pas d'autre solution ...
Mais personnellement, je n'aime pas m'appuyer à tout prix sur les principes oop. Créer des propriétés dans le seul but de rendre privées les membres de données.
(C #):
private _foo;
public foo
{
get {return _foo;}
set {_foo=value;}
}
C’était mon opinion personnelle.
Mais faites ce que votre patron exige (s'il veut des champs privés, faites-le.)
J'utilise des variables / attributs protégés dans des classes de base que je ne prévois pas de transformer en méthodes. De cette manière, les sous-classes ont un accès complet à leurs variables héritées et n'ont pas la charge (créée artificiellement) de passer par des getters / setters pour y accéder. Un exemple est une classe utilisant un flux d'E / S sous-jacent; il y a peu de raisons de ne pas autoriser l'accès direct des sous-classes au flux sous-jacent.
Cela convient très bien aux variables membres qui sont utilisées de manière simple et directe au sein de la classe de base et de toutes les sous-classes. Mais pour une variable dont l'utilisation est plus compliquée (par exemple, son accès provoque des effets secondaires chez d'autres membres de la classe), une variable directement accessible n'est pas appropriée. Dans ce cas, il peut être rendu privé et des getters / setters publics / protégés peuvent être fournis à la place. Un exemple est un mécanisme de mise en mémoire tampon interne fourni par la classe de base, dans lequel accéder directement aux tampons à partir d’une sous-classe compromettrait l’intégrité des algorithmes utilisés par la classe de base pour les gérer.
Il s’agit d’un jugement de conception, basé sur la simplicité de la variable membre et sur la manière dont elle devrait l'être dans les versions futures.
L’encapsulation est excellente, mais elle peut être poussée trop loin. J'ai vu des classes dont les méthodes privées ont accédé à ses variables de membre en utilisant uniquement les méthodes getter / setter. C'est exagéré, car si une classe ne peut pas faire confiance à ses propres méthodes privées avec ses propres données privées, à qui peut-elle faire confiance?