web-dev-qa-db-fra.com

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

Je suis entré dans un argument Internet intéressant sur les méthodes getter et setter et l'encapsulation. Quelqu'un a dit que tout ce qu'il devait faire était une affectation (setters) ou un accès variable (getters) pour les garder "purs" et assurer l'encapsulation.

  • Ai-je raison de dire que cela irait à l'encontre du but d'avoir des getters et des setters en premier lieu et que la validation et d'autres logiques (sans effets secondaires étranges bien sûr) devraient être autorisées?
  • Quand la validation doit-elle avoir lieu?
    • Lors de la définition de la valeur, à l'intérieur du setter (pour protéger l'objet de ne jamais entrer dans un état invalide - mon avis)
    • Avant de régler la valeur, en dehors du setter
    • À l'intérieur de l'objet, avant chaque utilisation de la valeur
  • Un setter est-il autorisé à modifier la valeur (peut-être convertir une valeur valide en une représentation interne canonique)?
45
Botond Balázs

Je me souviens d'avoir eu un argument similaire avec mon professeur lors de l'apprentissage du C++ à l'université. Je ne voyais tout simplement pas l'intérêt d'utiliser des getters et des setters quand je pouvais rendre une variable publique. 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 setters, vous fournirez une interface cohérente de sorte que si vous souhaitez modifier votre implémentation, vous êtes moins susceptible de casser le code dépendant. Cela est particulièrement important lorsque vos classes sont exposées via une API et utilisées dans d'autres applications ou par des tiers. Qu'en est-il des éléments qui entrent dans le getter ou le setter?

Les accesseurs sont généralement mieux implémentés comme un simple relais simplifié pour accéder à une valeur car cela rend leur comportement prévisible. Je dis généralement, car j'ai vu des cas où des getters 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. Il n'y a cependant 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 afin que la fonctionnalité du "getter" soit plus apparente lors de la lecture du code.

Comparez les éléments suivants:

int aValue = MyClass.Value;

et

int aValue = MyClass.CalculateValue();

La deuxième option indique clairement que la valeur est calculée, tandis que le premier exemple vous indique que vous renvoyez simplement une valeur sans rien savoir de la valeur elle-même.

Vous pourriez peut-être prétendre que ce qui suit serait plus clair:

int aValue = MyClass.CalculatedValue;

Le problème est cependant que vous supposez que la valeur a déjà été manipulée ailleurs. Donc, dans le cas d'un getter, bien que vous souhaitiez peut-être supposer que quelque chose d'autre se passe lorsque vous retournez une valeur, il est difficile de clarifier 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 setter fournisse un traitement supplémentaire afin de valider les données soumises à une propriété, lançant une exception si la définition d'une valeur violerait les limites définies de la propriété. Cependant, le problème que certains développeurs ont avec l'ajout de traitement aux setters est qu'il y a toujours une tentation de faire en sorte que le setter fasse un peu plus, comme effectuer un calcul ou une manipulation des données d'une manière ou d'une autre. C'est là que vous pouvez obtenir des effets secondaires qui peuvent dans certains cas être imprévisibles ou indésirables.

Dans le cas des setters, j'applique toujours une règle simple, qui consiste à faire le moins possible avec les données. Par exemple, j'autorise généralement les tests de limites et les arrondis afin que je puisse à la fois lever des exceptions si nécessaire, ou éviter des exceptions inutiles où elles peuvent être évitées de manière raisonnable. Les propriétés en virgule flottante sont un bon exemple où vous pouvez souhaiter arrondir les décimales excessives pour éviter de déclencher une exception, tout en permettant la saisie de valeurs dans la plage avec quelques décimales supplémentaires.

Si vous appliquez une sorte de manipulation de l'entrée du setter, vous avez le même problème qu'avec 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 quoi que ce soit qui va arriver à la valeur quand elle est donnée au setter?

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 indiquera pas si votre valeur va être arbitrairement modifiée. Lors de la lecture du code, le deuxième exemple sera beaucoup plus clair dans son but et sa fonction.

Ai-je raison de dire que cela irait à l'encontre du but d'avoir des getters et des setters en premier lieu et que la validation et d'autres logiques (sans effets secondaires étranges bien sûr) devraient être autorisées?

Avoir des getters et des setters ne consiste pas à encapsuler dans un souci de "pureté", mais à encapsuler afin de permettre au code d'être facilement refactorisé sans risquer une modification de l'interface de la classe qui autrement casserait la compatibilité de la classe avec le code appelant. La validation est tout à fait appropriée dans un setter, cependant il y a un petit risque est qu'une modification de la validation pourrait rompre la compatibilité avec le code appelant si le code appelant repose sur la validation se produisant d'une manière particulière. Il s'agit d'une situation généralement rare et à risque relativement faible, mais il convient de la noter par souci d'exhaustivité.

Quand la validation doit-elle avoir lieu?

La validation doit avoir lieu dans le contexte du setter 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 setter, afin de garder le code du setter relativement épuré.

Un setter est-il autorisé à modifier 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.

38
S.Robins

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 publiques certaines variables membres si vous avez une bonne raison. Si vous écrivez une classe de points 3D, avoir public .x, .y, .z est parfaitement logique.

Comme Ralph Waldo Emerson l'a dit, "Une cohérence stupide est le hobgobelin des petits esprits, adoré par les petits hommes d'État et les philosophes et les concepteurs de Java."

Les getters/setters sont utiles là où il peut y avoir des effets secondaires, où vous devez mettre à jour d'autres variables internes, recalculer les valeurs mises en cache et protéger la classe contre les entrées invalides.

La justification habituelle pour eux, qu'ils cachent la structure interne, est généralement la moins utile. par exemple. J'ai ces points stockés sous forme de 3 flottants, je pourrais décider de les stocker sous forme de chaînes dans une base de données distante, donc je ferai des getters/setters pour les cacher, comme si vous pouviez le faire sans avoir d'autre effet sur le code de l'appelant.

20
Martin Beckett

Principe d'accès uniforme de Meyer: "Tous les services offerts par un module doivent être disponibles via une notation uniforme, qui ne trahit pas s'ils sont mis en œuvre par le stockage ou par le calcul." est la principale raison derrière les getters/setters, alias 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 n'avez que des accesseurs de propriété exposés et non les données concrètes.

Les objets de valeur, les structures simples n'ont pas besoin de cette abstraction, mais une classe à part entière en a, à mon avis.

9
Karl

Une stratégie courante pour la conception de classes, qui a été introduite par le langage Eiffel, est Séparation Commande-Requête . L'idée est qu'une méthode devrait soit vous dire quelque chose sur l'objet ou dire à 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érez un objet de modèle de données qui est soutenu 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 fait en fait le SELECT. C'est bien, vous pouvez modifier certains détails internes sur la façon dont l'objet est représenté, mais vous ne changez pas la façon dont il apparaît aux clients de cet objet. Vous devriez être en mesure d'appeler des getters plusieurs fois et d'obtenir toujours les mêmes résultats, même s'ils effectuent un travail différent pour renvoyer ces résultats.

De même, un setter ressemble, contractuellement, comme s'il ne faisait que changer l'état d'un objet. Il peut le faire de façon compliquée: écrire un UPDATE dans une base de données ou transmettre le paramètre à un objet interne. C'est bien, mais faire quelque chose sans rapport avec l'établissement de l'État serait surprenant.

Meyer (le créateur d'Eiffel) avait également des choses à dire sur la validation. Essentiellement, chaque fois qu'un objet est au repos, il doit être dans un état valide. Donc, juste après la fin du constructeur, 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 pour appeler une procédure et pour lire un attribut exposé se ressemble. 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. Ce n'est que dans les langues qui ne cachent pas ce détail d'implémentation que la question se pose même: si les appelants ne pouvaient pas le dire, vous pouvez basculer entre un ivar public et les accesseurs sans divulguer cette modification dans le code client.

2
user4051

Une autre chose acceptable à faire est le clonage. Dans certains cas, vous devez vous assurer qu'après que 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 effectuez une copie complète du paramètre dans setter et renvoyez une copie complète dans getter. (L'utilisation de types immuables comme paramètres est une autre option, mais ci-dessus suppose que ce n'est pas possible) Mais ne clonez pas dans les accesseurs si ce n'est pas nécessaire. Il est facile de penser (mais pas de manière appropriée) aux propriétés/getters et setters en tant qu'opérations à coût constant, il s'agit donc d'une mine terrestre performante qui attend l'utilisateur de l'API.

1
user470365

Il n'y a pas toujours une 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 propriété center même s'il n'y a pas d'ivar qui stocke le centre de la vue; la définition de center provoque des modifications sur d'autres ivars, comme Origin ou transform ou autre chose, mais les clients de la classe ne savent pas ou ne se soucient pas commentcenter est stocké à condition qu'il fonctionne correctement. Ce qui devrait pas se produire, cependant, c'est que le réglage de center fait que les choses se produisent au-delà de tout ce qui est nécessaire pour enregistrer la nouvelle valeur, quelle que soit la manière.

1
Caleb

La meilleure partie des setters et des getters est qu'ils facilitent la modification des règles d'une API sans changer l'API. Si vous détectez un bogue, il est beaucoup plus probable que vous puissiez corriger le bogue dans la bibliothèque et que chaque consommateur ne mette pas à jour sa base de code.

0
AlexanderBrevig