Qu'est-ce qui devrait être permis à l'intérieur des getters et des setters?

45

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)?
Botond Balázs
la source
19
La meilleure chose à propos des getters et des setters est la possibilité de ne pas les avoir , c’est-à-dire de laisser le setter en lecture seule, de laisser le getter et vous avez l’option de configuration dont la valeur actuelle ne concerne personne.
Michael Borgwardt
7
@MichaelBorgwardt Laissez les deux personnes de côté pour avoir une interface propre «Dites, ne demandez pas». Propriétés = odeur potentielle de code.
Konrad Rudolph
2
Les setters peuvent également être utilisés pour organiser un événement .
John Isaiah Carmona
@ KonradRudolph Je suis d'accord avec votre déclaration, bien que je veuille vraiment insister sur le mot "potentiel".
Phil

Réponses:

37

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:

int aValue = MyClass.Value;

et

int aValue = MyClass.CalculateValue();

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:

int aValue = MyClass.CalculatedValue;

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:

MyClass.Value = 12345;

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:

MyClass.RoundValueToNearestThousand(12345);

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.

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?

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.

Quand la validation doit-elle avoir lieu?

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

Un installateur est-il autorisé à changer la valeur (peut-être convertir une valeur valide en une représentation interne canonique)?

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.

S.Robins
la source
re: changez la valeur, il pourrait être raisonnable de la mettre à -1 ou à un jeton d'indicateur NULL si le passeur reçoit une valeur illégale
Martin Beckett
1
Cela pose quelques problèmes. Définir arbitrairement une valeur crée un effet secondaire délibéré et peu clair. En outre, il ne permet pas au code de l'appelant de recevoir des informations qui pourraient être utilisées pour mieux traiter les données illégales. Ceci est particulièrement important avec les valeurs au niveau de l'interface utilisateur. Pour être juste, cependant, une exception à laquelle je viens de penser pourrait être si autoriser plusieurs formats de date comme entrée, tout en stockant la date dans un format de date / heure standard. On pourrait soutenir que cet "effet secondaire" particulier est une normalisation des données lors de la validation, à condition que les données saisies soient légales.
S.Robins
Oui, une des justifications d'un configurateur est qu'il doit renvoyer true / false si la valeur peut être définie.
Martin Beckett
1
"Les propriétés à virgule flottante sont un bon exemple où vous pourriez souhaiter arrondir les décimales excessives pour éviter de générer une exception" Dans quelles circonstances le fait d'avoir trop de décimales lève-t-il une exception?
Mark Byers
2
Je vois changer la valeur en une représentation canonique comme une forme de validation. Valeurs manquantes par défaut (par exemple, date actuelle si un horodatage ne contient que l'heure) ou mise à l'échelle d'une valeur (0,93275 -> 93,28%) pour assurer la cohérence interne devrait être OK, mais toute manipulation de ce type doit être explicitement appelée dans la documentation de l'API , surtout s’ils sont un langage inhabituel au sein de l’API.
TMN
20

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.

Martin Beckett
la source
1
Wow, les concepteurs de Java sont divins? </ jk>
@delnan - évidemment pas - ou ils rimeraient mieux ;-)
Martin Beckett
Serait-il valide de créer un point 3D dans lequel x, y, z sont tous Float.NAN?
Andrew T Finnell
4
Je ne partage pas votre point de vue selon lequel "la justification habituelle" (cacher la structure interne) est la propriété la moins utile des accesseurs et des passeurs. En C #, il est certainement utile de faire de la propriété une interface dont la structure sous-jacente peut être modifiée. Par exemple, si vous souhaitez uniquement qu'une propriété soit disponible pour l'énumération, il est bien mieux de le dire IEnumerable<T>que de le forcer à quelque chose de similaire List<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.
Brandon Linton
@ AndrewFinnell - cela pourrait être un bon moyen de marquer des points comme impossible / non valide / supprimé, etc.
Martin Beckett
9

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.

Karl
la source
2

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 UPDATEdans 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
1

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.

utilisateur470365
la source
1

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 centerpropriété même s'il n'y a pas d'ivar qui stocke le centre de la vue; la mise en centerchangements causes à d' autres Ivars, comme originou transformou autre, mais les clients de la classe ne savent pas ou se soucient comment center est stocké à condition que cela fonctionne correctement. Toutefois, ce qui ne devrait pas arriver, c’est que le réglage centerprovoque des choses qui vont bien au-delà de ce qui est nécessaire pour sauvegarder la nouvelle valeur, peu importe la situation.

Caleb
la source
0

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.

AlexanderBrevig
la source
-4

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.

m3th0dman
la source
1
Je pense que vous manquez le point de l'article, qui suggère d'utiliser des accesseurs / régleurs avec parcimonie et d'éviter d'exposer les données de classe sauf si nécessaire. Cet IMHO est un principe de conception raisonnable qui devrait être appliqué délibérément par le développeur. En termes de création de propriétés sur une classe, vous pouvez simplement exposer une variable, mais cela rend plus difficile de valider la validation lorsque la variable est définie ou de tirer la valeur d’une autre source lorsqu'elle est "got", violant essentiellement l’encapsulation, et potentiellement vous enfermant dans une interface difficile à maintenir.
S.Robins
@ S. Robinson À mon avis, les getters et les setters publics ont tort; les champs publics sont plus faux (si cela est comparable).
m3th0dman
@methodman Oui, je suis d'accord pour dire qu'un champ public est mauvais, mais une propriété publique peut être utile. Cette propriété peut être utilisée pour fournir un emplacement pour la validation ou des événements liés au paramétrage ou au renvoi des données, en fonction des besoins spécifiques du moment. Getters et setters eux-mêmes ne sont pas faux en soi. Comment et quand ils sont utilisés ou maltraités d’autre part peuvent être considérés comme médiocres en termes de conception et de maintenabilité en fonction des circonstances. :)
S.Robins
@ S.Robins Réfléchissez aux bases de la POO et de la modélisation; les objets sont censés copier des entités de la vie réelle. Y a-t-il des entités réelles qui ont ce type d'opérations / propriétés telles que des accesseurs / des setters?
m3th0dman
Pour éviter d'avoir trop de commentaires ici, je vais prendre cette conversation dans ce chat et y adresser votre commentaire.
S.Robins