Je me suis lancé dans une discussion Internet intéressante sur les méthodes d’encapsulation et de getter et de setter. Quelqu'un a dit qu'ils ne devraient faire qu'une assignation (setters) ou un accès variable (getters) pour les garder "purs" et assurer l'encapsulation.
- Ai-je raison de penser que cela irait complètement à l'encontre du but recherché, à savoir que les getters et les setters soient autorisés en premier lieu, et que la validation et toute autre logique (sans effets secondaires étranges bien sûr) soient autorisées?
- Quand la validation doit-elle avoir lieu?
- Lors du réglage de la valeur, à l'intérieur du séparateur (pour protéger l'objet de toute entrée invalide - à mon avis)
- Avant de définir la valeur, en dehors du setter
- À l'intérieur de l'objet, avant chaque utilisation de la valeur
- Un installateur est-il autorisé à changer la valeur (peut-être convertir une valeur valide en une représentation interne canonique)?
object-oriented
language-agnostic
encapsulation
Botond Balázs
la source
la source
Réponses:
Je me souviens d'avoir eu un argument similaire avec mon conférencier lors de l'apprentissage de C ++ à l'université. Je ne pouvais tout simplement pas comprendre l'intérêt d'utiliser des accesseurs et des setters quand je pouvais rendre un public variable. Je comprends mieux maintenant avec des années d’expérience et j’ai appris une meilleure raison que de simplement dire "pour maintenir l’encapsulation".
En définissant les getters et les setters, vous fournissez une interface cohérente de sorte que, si vous souhaitez modifier votre implémentation, vous aurez moins de chances de casser du code dépendant. Ceci est particulièrement important lorsque vos classes sont exposées via une API et utilisées dans d'autres applications ou par des tiers. Alors, qu'en est-il des choses qui vont dans le getter ou le setter?
Les accesseurs sont généralement mieux mis en œuvre en tant que simple passe-partout simplifié pour accéder à une valeur, car cela rend leur comportement prévisible. Je dis en général, car j’ai vu des cas où des accesseurs ont été utilisés pour accéder à des valeurs manipulées par calcul ou même par code conditionnel. Généralement pas si bon si vous créez des composants visuels pour une utilisation au moment de la conception, mais apparemment pratique au moment de l'exécution. Cependant, il n'y a pas de réelle différence entre cela et l'utilisation d'une méthode simple, sauf que lorsque vous utilisez une méthode, vous êtes généralement plus susceptible de nommer une méthode de manière plus appropriée, de sorte que la fonctionnalité du "getter" soit plus apparente lors de la lecture du code.
Comparez ce qui suit:
et
La deuxième option indique clairement que la valeur est en cours de calcul, alors que le premier exemple vous indique que vous renvoyez simplement une valeur sans rien connaître de la valeur elle-même.
Vous pourriez peut-être soutenir que ce qui suit serait plus clair:
Le problème, cependant, est que vous supposez que la valeur a déjà été manipulée ailleurs. Ainsi, dans le cas d'un getter, bien que vous souhaitiez supposer que quelque chose d'autre se passe lorsque vous renvoyez une valeur, il est difficile de préciser ces choses dans le contexte d'une propriété et les noms de propriété ne doivent jamais contenir de verbes. sinon, il est difficile de comprendre d'un coup d'œil si le nom utilisé doit être décoré de parenthèses lors de l'accès.
Les Setters sont un cas légèrement différent cependant. Il est tout à fait approprié qu'un ouvreur fournisse un traitement supplémentaire afin de valider les données soumises à une propriété, en levant une exception si définir une valeur enfreindrait les limites définies de la propriété. Le problème rencontré par certains développeurs lors de l’ajout de traitement aux paramètres est toutefois qu’il est toujours tentant de laisser un peu plus de travail, par exemple effectuer un calcul ou une manipulation des données. C’est là que vous pouvez avoir des effets secondaires qui peuvent parfois être imprévisibles ou indésirables.
Dans le cas des setters, j'applique toujours une règle simple, qui consiste à utiliser le moins possible les données. Par exemple, j'autorise généralement les tests des limites et les arrondis afin de pouvoir lever des exceptions si nécessaire ou d'éviter des exceptions inutiles lorsque celles-ci peuvent être évitées de manière raisonnable. Les propriétés de virgule flottante sont un bon exemple où vous pouvez arrondir le nombre de décimales excessif pour éviter de générer une exception, tout en permettant de saisir les valeurs de plage avec quelques décimales supplémentaires.
Si vous appliquez une sorte de manipulation de l'entrée du setter, vous rencontrez le même problème que pour le getter: il est difficile de permettre aux autres de savoir ce que fait le setter en le nommant simplement. Par exemple:
Est-ce que cela vous dit quelque chose sur ce qui va arriver à la valeur quand elle est donnée au passeur?
Que diriez-vous:
Le deuxième exemple vous indique exactement ce qui va arriver à vos données, tandis que le premier ne vous laissera pas savoir si votre valeur va être modifiée de manière arbitraire. Lors de la lecture du code, le deuxième exemple sera beaucoup plus clair dans son but et sa fonction.
Avoir des getters et des setters ne concerne pas l’encapsulation pour des raisons de «pureté», mais l’encapsulation afin de permettre au code d’être facilement refactorisé sans risquer de modifier l’interface de la classe qui risquerait sinon de briser la compatibilité de la classe avec le code appelant. La validation est tout à fait appropriée dans un setter, mais il existe un faible risque qu'une modification de la validation puisse rompre la compatibilité avec le code appelant si le code appelant repose sur la validation effectuée de manière particulière. C’est une situation généralement rare et présentant un risque relativement faible, mais il convient de le noter pour des raisons de complétude.
La validation doit avoir lieu dans le contexte du paramètre avant de définir réellement la valeur. Cela garantit que si une exception est levée, l'état de votre objet ne changera pas et invalidera potentiellement ses données. Je trouve généralement préférable de déléguer la validation à une méthode distincte, qui serait la première chose appelée dans le programme de définition, afin de garder le code de définition relativement peu encombré.
Dans de très rares cas, peut-être. En général, il vaut probablement mieux ne pas le faire. C'est le genre de chose qu'il vaut mieux laisser à une autre méthode.
la source
Si le getter / setter reflète simplement la valeur, il est inutile de les avoir ou de rendre la valeur privée. Il n'y a rien de mal à rendre certaines variables membres publiques si vous avez une bonne raison. Si vous écrivez une classe de points 3D, il est tout à fait logique de disposer de .x, .y, .z public.
Comme le disait Ralph Waldo Emerson, "une sotte consistance est le hobgoblin des petits esprits, adoré des petits hommes d'État et des philosophes et des concepteurs de Java".
Les getters / setters sont utiles en cas d’effets secondaires, pour lesquels vous devez mettre à jour d’autres variables internes, recalculer les valeurs en cache et protéger la classe des entrées non valides.
Leur justification habituelle, à savoir qu'elles cachent la structure interne, est généralement la moins utile. par exemple. J'ai ces points stockés sous forme de 3 floats, je pourrais décider de les stocker sous forme de chaînes dans une base de données distante, je vais donc créer des accesseurs pour les masquer, comme si vous pouviez le faire sans que cela affecte le code de l'appelant.
la source
IEnumerable<T>
que de le forcer à quelque chose de similaireList<T>
. Je dirais que votre exemple d’accès à une base de données va à l’encontre de la responsabilité unique, en mélangeant la représentation du modèle avec sa persistance.Principe d'accès uniforme de Meyer: "Tous les services offerts par un module devraient être disponibles au moyen d'une notation uniforme, qui ne permet pas de savoir s'ils sont mis en œuvre par stockage ou par calcul." est la raison principale derrière les getters / setters, aka Properties.
Si vous décidez de mettre en cache ou de calculer paresseusement un champ d'une classe, vous pouvez le modifier à tout moment si vous ne disposez que des accesseurs de propriétés exposées et non des données concrètes.
Les objets de valeur, les structures simples n'ont pas besoin de cette abstraction, mais une classe à part entière, à mon avis.
la source
Une stratégie commune pour la conception de classes, introduite à travers le langage Eiffel, est la séparation commande-requête . L'idée est qu'une méthode devrait soit vous dire quelque chose à propos de l'objet, soit indiquer à l'objet de faire quelque chose, mais pas de faire les deux.
Cela ne concerne que l'interface publique de la classe, pas la représentation interne. Considérons un objet de modèle de données qui est sauvegardé par une ligne dans la base de données. Vous pouvez créer l'objet sans charger les données, puis la première fois que vous appelez un getter, il exécute le
SELECT
. Très bien, vous modifiez peut-être certains détails internes sur la manière dont l'objet est représenté, mais vous ne modifiez pas son apparence pour les clients de cet objet. Vous devriez pouvoir appeler plusieurs fois les accesseurs et obtenir les mêmes résultats, même s'ils effectuent un travail différent pour obtenir ces résultats.De la même manière, un setter semble, contractuellement, comme s'il ne faisait que changer l'état d'un objet. Cela peut se faire de façon compliquée - écrire une
UPDATE
dans une base de données ou transmettre le paramètre à un objet interne. C'est bien, mais il serait étonnant de faire quelque chose qui ne soit pas lié à l'établissement de l'État.Meyer (le créateur d’Eiffel) a également parlé de la validation. Essentiellement, chaque fois qu'un objet est au repos, il devrait être dans un état valide. Ainsi, juste après que le constructeur ait fini, avant et après (mais pas nécessairement pendant) chaque appel de méthode externe, l'état de l'objet doit être cohérent.
Il est intéressant de noter que dans ce langage, la syntaxe utilisée pour appeler une procédure et pour lire un attribut exposé est identique. En d'autres termes, un appelant ne peut pas dire s'il utilise une méthode ou s'il travaille directement avec une variable d'instance. La question ne se pose que dans des langues qui ne cachent pas ce détail d'implémentation. Si les appelants ne pouvaient pas le savoir, vous pouvez basculer entre un ivar public et des accesseurs sans transférer ce changement dans le code client.
la source
Une autre chose acceptable à faire est le clonage. Dans certains cas, vous devez vous assurer qu'après, quelqu'un donne votre classe, par exemple. une liste de quelque chose, il ne peut pas le changer dans votre classe (ni changer l'objet dans cette liste). Par conséquent, vous créez une copie complète du paramètre dans setter et renvoyez une copie complète dans getter. (Utiliser des types immuables en tant que paramètres est l'option annother, mais ce qui précède suppose que ce n'est pas possible) Mais ne clonez pas dans les accesseurs si cela n'est pas nécessaire. Il est facile de penser (mais pas de manière appropriée) aux propriétés, aux getters et aux setters en tant qu’opérations à coûts constants. Il s’agit donc d’une mine terrestre performante en attente de l’utilisateur de l’API.
la source
Il n'y a pas toujours de correspondance 1-1 entre les accesseurs de propriété et les ivars qui stockent les données.
Par exemple, une classe de vue peut fournir une
center
propriété même s'il n'y a pas d'ivar qui stocke le centre de la vue; la mise encenter
changements causes à d' autres Ivars, commeorigin
outransform
ou autre, mais les clients de la classe ne savent pas ou se soucient commentcenter
est stocké à condition que cela fonctionne correctement. Toutefois, ce qui ne devrait pas arriver, c’est que le réglagecenter
provoque des choses qui vont bien au-delà de ce qui est nécessaire pour sauvegarder la nouvelle valeur, peu importe la situation.la source
La meilleure chose à propos des setters et des getters est qu’ils facilitent la modification des règles d’une API sans la modifier. Si vous détectez un bogue, il est beaucoup plus probable que vous puissiez le corriger dans la bibliothèque sans que chaque consommateur ne mette à jour sa base de code.
la source
J'ai tendance à croire que les setters et les getters sont diaboliques et qu'ils ne devraient être utilisés que dans des classes gérées par un cadre / conteneur. Une conception de classe appropriée ne doit pas donner d’obstacles ni de setters.
Edit: un article bien écrit sur ce sujet .
Edit2: les champs publics sont un non-sens dans une approche POO; en disant que les Getters et les Setters sont diaboliques, je ne veux pas dire qu’ils devraient être remplacés par des terrains publics.
la source