Je ne cherche pas une opinion sur la sémantique, mais simplement un cas où avoir des getters judicieusement utilisés est un réel obstacle. Peut-être que cela me jette dans une spirale sans fin de compter sur eux, peut-être que l'alternative est plus propre et gère les getters automatiquement, etc. Quelque chose de concret.
J'ai entendu tous les arguments, j'ai entendu dire qu'ils sont mauvais parce qu'ils vous forcent à traiter des objets comme des sources de données, qu'ils violent «l'état pur» d'un objet de «ne donnez pas trop mais soyez prêt à accepter beaucoup ".
Mais absolument aucune raison raisonnable pour laquelle a getData
est une mauvaise chose, en fait, quelques personnes ont fait valoir que c'est beaucoup de sémantique, des getters aussi bien en soi, mais ne les nommez pas getX
, pour moi, c'est au moins drôle .
Quelle est une chose, sans opinions, qui se brisera si j'utilise les getters de manière sensée et pour les données qui, de toute évidence, l'intégrité de l'objet ne se brise pas s'il le met hors tension?
Bien sûr, autoriser un getter pour une chaîne utilisée pour crypter quelque chose est au-delà de la stupidité, mais je parle des données dont votre système a besoin pour fonctionner. Peut-être que vos données sont tirées à travers un Provider
de l'objet, mais, encore, l'objet doit toujours autoriser le Provider
à faire un $provider[$object]->getData
, il n'y a aucun moyen de le contourner.
Pourquoi je demande: Pour moi, les getters, lorsqu'ils sont utilisés de manière raisonnable et sur des données qui sont traitées comme "sûres" sont envoyés par Dieu, 99% de mes getters sont utilisés pour identifier l'objet, comme dans, je demande, par le biais du code Object, what is your name? Object, what is your identifier?
, quiconque travaille avec un objet devrait savoir ces choses sur un objet, car presque tout ce qui concerne la programmation est une identité et qui d'autre sait mieux ce que c'est que l'objet lui-même? Je ne vois donc aucun problème réel, sauf si vous êtes un puriste.
J'ai regardé toutes les questions de StackOverflow sur "pourquoi les getters / setters" sont mauvais et bien que je convienne que les setters sont vraiment mauvais dans 99% des cas, les getters n'ont pas à être traités de la même manière juste parce qu'ils riment.
Un setter compromettra l'identité de votre objet et rendra très difficile le débogage de qui modifie les données, mais un getter ne fait rien.
la source
Réponses:
Vous ne pouvez pas écrire du bon code sans les getters.
La raison n'est pas parce que les getters ne cassent pas l'encapsulation, ils le font. Ce n'est pas parce que les getters ne tentent pas les gens de ne pas prendre la peine de suivre la POO, ce qui leur ferait mettre des méthodes avec les données sur lesquelles ils agissent. Ils font. Non, vous avez besoin de getters à cause des limites.
Les idées d'encapsulation et de conservation des méthodes avec les données sur lesquelles elles agissent ne fonctionnent tout simplement pas lorsque vous rencontrez une limite qui vous empêche de déplacer une méthode et vous oblige donc à déplacer des données.
C'est vraiment aussi simple que cela. Si vous utilisez des getters quand il n'y a pas de limite, vous finissez par ne pas avoir de vrais objets. Tout commence à tendre à la procédure. Ce qui fonctionne aussi bien que jamais.
La vraie POO n'est pas quelque chose que vous pouvez répandre partout. Cela ne fonctionne que dans ces limites.
Ces frontières ne sont pas minces. Ils contiennent du code. Ce code ne peut pas être OOP. Il ne peut pas non plus être fonctionnel. Non, ce code a nos idéaux dépouillés de sorte qu'il puisse faire face à la dure réalité.
Michael Fetters a appelé ce fascia de code après ce tissu conjonctif blanc qui maintient ensemble des sections d'une orange.
C'est une merveilleuse façon d'y penser. Cela explique pourquoi il est acceptable d'avoir les deux types de code dans la même base de code. Sans cette perspective, de nombreux nouveaux programmeurs s'accrochent durement à leurs idéaux, puis ont le cœur brisé et abandonnent ces idéaux lorsqu'ils atteignent leur première frontière.
Les idéaux ne fonctionnent qu'à leur place. Ne les abandonnez pas simplement parce qu'ils ne fonctionnent pas partout. Utilisez-les là où ils travaillent. Cet endroit est la partie juteuse que le fascia protège.
Un exemple simple de frontière est une collection. Cela contient quelque chose et n'a aucune idée de ce que c'est. Comment un concepteur de collection peut-il déplacer la fonctionnalité comportementale de l'objet détenu dans la collection alors qu'il n'a aucune idée de ce qu'il va contenir? Tu ne peux pas. Vous êtes contre une frontière. C'est pourquoi les collections ont des getters.
Maintenant, si vous le saviez, vous pourriez modifier ce comportement et éviter de bouger. Quand vous savez, vous devriez. Tu ne sais pas toujours.
Certaines personnes appellent cela simplement pragmatique. Et c'est. Mais c'est bien de savoir pourquoi nous devons être pragmatiques.
Vous avez dit que vous ne vouliez pas entendre d'arguments sémantiques et que vous sembliez préconiser de mettre des «getters sensés» partout. Vous demandez que cette idée soit contestée. Je pense que je peux montrer que l'idée a des problèmes avec la façon dont vous l'avez formulée. Mais je pense aussi que je sais d'où tu viens parce que j'y suis allé.
Si vous voulez des getters partout, regardez Python. Il n'y a pas de mot clé privé. Pourtant, Python fonctionne très bien. Comment? Ils utilisent une astuce sémantique. Ils nomment tout ce qui est censé être privé avec un soulignement de premier plan. Vous êtes même autorisé à en lire à condition d'en assumer la responsabilité. "Nous sommes tous des adultes ici", disent-ils souvent.
Alors, quelle est la différence entre cela et simplement mettre des getters sur tout en Java ou C #? Désolé mais c'est de la sémantique. Les conventions Pythons soulignent clairement que vous fouillez derrière la porte réservée aux employés. Slap getters sur tout et vous perdez ce signal. Avec la réflexion, vous auriez pu de toute façon dépouillé le privé et ne pas avoir perdu le signal sémantique. Il n'y a tout simplement aucun argument structurel à faire valoir ici.
Il nous reste donc à décider où accrocher le panneau "Employés uniquement". Qu'est-ce qui devrait être considéré comme privé? Vous appelez cela des «getters sensés». Comme je l'ai dit, la meilleure justification pour un getter est une frontière qui nous éloigne de nos idéaux. Cela ne devrait pas aboutir à des getters sur tout. Quand cela aboutit à un getter, vous devriez envisager de déplacer le comportement plus loin dans le morceau juteux où vous pouvez le protéger.
Cette séparation a donné lieu à quelques termes. Un objet de transfert de données ou DTO, ne possède aucun comportement. Les seules méthodes sont des getters et parfois des setters, parfois un constructeur. Ce nom est regrettable car ce n'est pas du tout un véritable objet. Les getters et setters ne sont en fait que du débogage de code qui vous donne un endroit pour définir un point d'arrêt. Si ce n'était pas pour ce besoin, ils ne seraient qu'un tas de champs publics. En C ++, nous les appelions des structs. La seule différence qu'ils avaient d'une classe C ++ était qu'ils étaient par défaut public.
Les DTO sont agréables parce que vous pouvez les jeter sur un mur d'enceinte et conserver vos autres méthodes en toute sécurité dans un bel objet de comportement juteux. Un vrai objet. Sans getters pour violer, c'est l'encapsulation. Mes objets comportementaux peuvent manger des DTO en les utilisant comme objets paramètres . Parfois, je dois en faire une copie défensive pour empêcher un état mutable partagé . Je ne diffuse pas de DTO mutables à l'intérieur de la partie juteuse à l'intérieur de la frontière. Je les résume. Je les cache. Et quand je rencontre enfin une nouvelle frontière, je tourne un nouveau DTO et je le jette sur le mur, ce qui en fait le problème de quelqu'un d'autre.
Mais vous voulez fournir des getters qui expriment l'identité. Eh bien, félicitations, vous avez trouvé une limite. Les entités ont une identité qui dépasse leur référence. Autrement dit, au-delà de leur adresse mémoire. Il faut donc le stocker quelque part. Et quelque chose doit pouvoir se référer à cette chose par son identité. Un getter qui exprime une identité est parfaitement raisonnable. Une pile de code qui utilise ce getter pour prendre des décisions que l'entité aurait pu prendre elle-même ne l'est pas.
En fin de compte, ce n'est pas l'existence de getters qui a tort. Ils sont bien meilleurs que les domaines publics. Ce qui est mauvais, c'est quand ils sont utilisés pour prétendre que vous êtes orienté objet alors que vous ne l'êtes pas. Les Getters sont bons. Être orienté objet est bon. Les getters ne sont pas orientés objet. Utilisez des getters pour tailler un endroit sûr pour être orienté objet.
la source
clearly
? Si des données me sont transmises par quelque chose, par valeur , clairement, mon objet possède maintenant les données, mais par sémantique, il ne le fait pas encore, à ce stade, il a simplement obtenu les données de quelqu'un d'autre. Il doit ensuite effectuer des opérations pour transformer ces données, les faire siennes, au moment où les données sont touchées, vous devez apporter des modifications sémantiques: vous avez nommé les données comme$data_from_request
et maintenant vous les avez opérées? Nommez-le$changed_data
. Vous souhaitez exposer ces données à d'autres? Créez un gettergetChangedRequestData
, puis la propriété est clairement définie. Ou est-ce?Les Getters violent le principe d'Hollywood ("Ne nous appelez pas, nous vous appellerons")
Le principe d'Hollywood (alias Inversion of Control) stipule que vous n'appelez pas dans le code de bibliothèque pour faire avancer les choses; le framework appelle plutôt votre code. Parce que le framework contrôle les choses, la diffusion de son état interne à ses clients n'est pas nécessaire. Tu n'as pas besoin de savoir.
Dans sa forme la plus insidieuse, violer le principe d'Hollywood signifie que vous utilisez un getter pour obtenir des informations sur l'état d'une classe, puis que vous décidez des méthodes pour appeler cette classe en fonction de la valeur que vous obtenez. c'est une violation de l'encapsulation à son meilleur.
L'utilisation d'un getter implique que vous avez besoin de cette valeur, alors que ce n'est pas le cas.
Vous pourriez en fait avoir besoin de cette amélioration des performances
Dans les cas extrêmes d'objets légers qui doivent avoir les performances maximales possibles, il est possible (bien qu'extrêmement improbable) que vous ne puissiez pas payer la très faible pénalité de performance imposée par un getter. Cela n'arrivera pas 99,9% du temps.
la source
Generator
objet qui parcourt tous mesItems
objets, puis appellegetName
de chacunItem
pour faire quelque chose de plus. Quel est le problème avec ça? Puis, en retour, leGenerator
crache des chaînes formatées. C'est dans mon cadre, pour lequel j'ai ensuite une API en plus que les gens peuvent utiliser pour exécuter tout ce que les utilisateurs fournissent mais sans toucher au cadre.What is the issue with this?
Aucun que je puisse voir. C'est essentiellement ce que fait unemap
fonction. Mais ce n'est pas la question que vous avez posée. Vous avez essentiellement demandé: «Existe-t-il des conditions dans lesquelles un getter peut être déconseillé». J'ai répondu avec deux, mais cela ne signifie pas que vous abandonnez complètement les colons.Détails de mise en œuvre des fuites Getters et rupture de l'abstraction
Considérer
public int millisecondsSince1970()
Vos clients penseront "oh, c'est un int" et il y aura des milliers d'appels en supposant que c'est un int , en faisant des mathématiques entières en comparant les dates, etc ... Quand vous réaliserez que vous avez besoin d'un long , il y aura beaucoup du code hérité avec des problèmes. Dans un monde Java, vous ajouteriez
@deprecated
à l'API, tout le monde l'ignorerait et vous êtes bloqué en maintenant un code obsolète et bogué. :-( (je suppose que les autres langues sont similaires!)Dans ce cas particulier, je ne sais pas quelle meilleure option pourrait être, et la réponse @candiedorange est complètement sur le point, il y a plusieurs fois où vous avez besoin de getters, mais cet exemple illustre les inconvénients. Chaque getter public a tendance à vous "enfermer" dans une certaine implémentation. Utilisez-les si vous avez vraiment besoin de «franchir les frontières», mais utilisez-les le moins possible et avec soin et prévoyance.
la source
timepoint
ettimespan
respectivement. (epoch
est une valeur particulière detimepoint
.) Sur ces classes, ils fournissent des getters tels quegetInt
orgetLong
ougetDouble
. Si les classes qui manipulent le temps sont écrites de manière à (1) déléguer l'arithmétique liée au temps à ces objets et méthodes, et (2) donner accès à ces objets temporels via des getters, le problème est résolu, sans avoir besoin de se débarrasser des getters .Je pense que la première clé est de se rappeler que les absolus ont toujours tort.
Envisagez un programme de carnet d'adresses, qui stocke simplement les coordonnées des personnes. Mais, faisons-le bien et divisons-le en couches contrôleur / service / référentiel.
Il y aura une
Person
classe qui contient des données réelles: nom, adresse, numéro de téléphone, etc.Address
et quiPhone
sont également des classes, afin que nous puissions utiliser le polymorphisme plus tard pour prendre en charge les schémas non américains. Ces trois classes sont des objets de transfert de données , elles ne seront donc que des getters et des setters. Et, cela a du sens: ils ne vont pas avoir de logique en dehors de l'emporterequals
etgetHashcode
; leur demander de vous donner une représentation des informations d'une personne complète n'a pas de sens: est-ce pour une présentation HTML, une application GUI personnalisée, une application console, un point de terminaison HTTP, etc.?C'est là que le contrôleur, le service et le référentiel entrent en jeu. Toutes ces classes vont être logiques et légères, grâce à l'injection de dépendances.
Le contrôleur va obtenir un service injecté, qui exposera des méthodes comme
get
etsave
, qui prendront toutes les deux des arguments raisonnables (par exemple,get
pourraient avoir des remplacements pour rechercher par nom, rechercher par code postal, ou tout simplement obtenir tout;save
prendra probablement un seulPerson
et enregistrez-le). Quels getters aurait le contrôleur? La possibilité d'obtenir le nom de la classe concrète peut être utile pour la journalisation, mais quel état tiendra-t-il autre que l'état qui lui a été injecté?De même, la couche de service obtiendra un référentiel injecté, donc elle peut réellement obtenir et persister
Person
, et elle pourrait avoir une certaine "logique métier" (par exemple, nous allons peut-être exiger que tous lesPerson
objets aient au moins une adresse ou un téléphone nombre). Mais, encore une fois: dans quel état cet objet a-t-il autre que celui injecté? Encore une fois, aucun.La couche de référentiel est l'endroit où les choses deviennent un peu plus intéressantes: elle va établir une connexion avec un emplacement de stockage, par exemple. un fichier, une base de données SQL ou un magasin en mémoire. Il est tentant, ici, d'ajouter un getter pour obtenir le nom de fichier ou la chaîne de connexion SQL, mais c'est cet état qui a été injecté dans la classe. Le référentiel doit-il avoir un getter pour indiquer s'il est correctement connecté à son magasin de données? Eh bien, peut-être, mais à quoi servent ces informations en dehors de la classe de référentiel elle-même? La classe de référentiel elle-même devrait pouvoir tenter de se reconnecter à son magasin de données si nécessaire, ce qui suggère que la
isConnected
propriété a déjà une valeur douteuse. De plus, uneisConnected
propriété va probablement se tromper au moment où elle aurait le plus besoin d'être correcte: vérificationisConnected
avant d'essayer d'obtenir / stocker des données ne garantit pas que le référentiel sera toujours connecté lorsque l'appel "réel" est effectué, il ne supprime donc pas la nécessité de gérer les exceptions quelque part (il existe des arguments pour savoir où cela devrait aller, au-delà la portée de cette question).Pensez également aux tests unitaires (vous écrivez des tests unitaires, n'est-ce pas?): Le service ne s'attend pas à ce qu'une classe spécifique soit injectée, il va plutôt s'attendre à ce qu'une implémentation concrète d'une interface soit injectée. Cela permet à l'application dans son ensemble de changer l'endroit où les données sont stockées sans rien faire d'autre que d'échanger le référentiel injecté dans le service. Il permet également au service d'être testé unitaire en injectant un référentiel factice. Cela signifie penser en termes d'interfaces plutôt que de classes. L'interface du référentiel exposerait-elle des getters? Autrement dit, existe-t-il un état interne qui serait: commun à tous les référentiels, utile en dehors du référentiel et non injecté dans le référentiel? J'aurais du mal à penser à tout moi-même.
TL; DR
En conclusion: à part les classes dont le seul but est de transporter des données, rien d'autre dans la pile n'a de place pour mettre un getter: l'état est soit injecté soit hors de propos en dehors de la classe ouvrière.
la source
Membres mutables.
Si vous avez une collection de choses dans un objet que vous exposez via un getter, vous vous exposez potentiellement à des bogues associés aux mauvaises choses ajoutées à la collection.
Si vous avez un objet mutable que vous exposez via un getter, vous vous ouvrez potentiellement à l'état interne de votre objet modifié d'une manière que vous ne vous attendez pas.
Un très mauvais exemple serait un objet qui compte de 1 à 100 exposant sa valeur actuelle comme référence mutable. Cela permet à un objet tiers de modifier la valeur de Current de telle sorte que la valeur puisse se trouver en dehors des limites attendues.
C'est principalement un problème avec l'exposition des objets mutables. L'exposition de structures ou d'objets immuables n'est pas du tout un problème, car toute modification de ceux-ci modifiera une copie à la place (généralement soit par une copie implicite dans le cas d'une structure, soit par une copie explicite dans le cas d'un objet immuable).
Pour utiliser une métaphore qui pourrait faciliter la compréhension de la différence.
Une fleur a un certain nombre de choses uniques à son sujet. Il a un
Color
et il a un certain nombre de pétales (NumPetals). Si j'observe cette fleur, dans le monde réel, je peux clairement voir sa couleur. Je peux alors prendre des décisions basées sur cette couleur. Par exemple, si la couleur est noire, ne donnez pas à la petite amie mais si la couleur est rouge, donnez-la à la petite amie. Dans notre modèle d'objet, la couleur de la fleur serait exposée comme un getter sur l'objet fleur. Mon observation de cette couleur est importante pour les actions que je vais effectuer, mais elle n'a aucun impact sur l'objet fleur. Je ne devrais pas pouvoir changer la couleur de cette fleur. De même, il n'est pas logique de masquer la propriété color. Une fleur ne peut généralement pas empêcher les gens d'observer sa couleur.Si je teinte la fleur, je devrais appeler la méthode ReactToDye (Color dyeColor) sur la fleur, qui changera la fleur selon ses règles internes. Je peux alors interroger à
Color
nouveau la propriété et réagir à tout changement après la méthode ReactToDye a été appelée. Il serait erroné pour moi de modifier directement leColor
de la fleur et si je pouvais alors l'abstraction est tombée en panne.Parfois (moins fréquemment, mais toujours assez souvent pour que cela mérite d'être mentionné) les setters sont une conception OO tout à fait valide. Si j'ai un objet Client, il est tout à fait valable d'exposer un setter pour son adresse. Que vous appeliez cela
setAddress(string address)
ouChangeMyAddressBecauseIMoved(string newAddress)
simplement,string Address { get; set; }
c'est une question de sémantique. L'état interne de cet objet doit changer et la manière appropriée de le faire est de définir l'état interne de cet objet. Même si j'ai besoin d'un enregistrement historique des adresses dans lesquellesCustomer
j'ai vécu, je peux utiliser le setter pour changer mon état interne de manière appropriée. Dans ce cas, il n'est pas logique que leCustomer
soit immuable et il n'y a pas de meilleur moyen de changer une adresse que de fournir un setter pour le faire.Je ne sais pas qui suggère que les Getters et les setters sont mauvais ou brisent les paradigmes orientés objet, mais s'ils le font, ils le font probablement en réponse à une caractéristique de langage spécifique de la langue qu'ils utilisent. J'ai le sentiment que cela est né de la culture Java "tout est un objet" (et s'est peut-être étendu à d'autres langages). Le monde .NET n'a pas du tout cette discussion. Les Getters et Setters sont une fonctionnalité de langage de première classe qui est utilisée non seulement par les personnes écrivant des applications dans la langue, mais également par l'API de langue elle-même.
Vous devez être prudent lorsque vous utilisez des getters. Vous ne voulez pas exposer les mauvais éléments de données et vous ne voulez pas non plus exposer les données de la mauvaise manière (c'est-à-dire des objets mutables, des références à des structures qui peuvent permettre à un consommateur de modifier l'état interne). Mais vous voulez modéliser vos objets afin que les données soient encapsulées de telle manière que vos objets puissent être utilisés par des consommateurs externes et protégés contre toute utilisation abusive par ces mêmes consommateurs. Cela nécessitera souvent des getters.
Pour résumer: les Getters et Setters sont des outils de conception orientés objet valides. Ils ne doivent pas être utilisés si généreusement que tous les détails de l'implémentation sont exposés, mais vous ne voulez pas non plus les utiliser avec parcimonie que les consommateurs d'un objet ne peuvent pas utiliser l'objet efficacement.
la source
La maintenabilité se brisera.
Chaque fois (je devrais dire presque toujours) que vous offrez un getter que vous donnez plus que ce qui vous a été demandé. Cela signifie également que vous devez maintenir plus que ce qui était demandé, ergo vous diminuez la maintenabilité sans raison réelle.
Allons avec l'
Collection
exemple de @ candied_orange . Contrairement à ce qu'il écrit,Collection
il ne devrait pas y avoir de getter. Les collections existent pour des raisons très spécifiques, notamment pour itérer sur tous les articles. Cette fonctionnalité n'est une surprise pour personne, donc elle devrait être implémentée dans leCollection
, au lieu de pousser ce cas d'utilisation évident sur l'utilisateur, en utilisant des cycles et des getters ou autres joyeusetés.Pour être juste, certaines limites ne getters besoin. Cela se produit si vous ne savez vraiment pas à quoi ils serviront. Par exemple, vous pouvez obtenir par programme la trace de pile de la
Exception
classe Java de nos jours, parce que les gens voulaient l'utiliser pour toutes sortes de raisons curieuses que les auteurs n'avaient pas ou ne pouvaient pas imaginer.la source
Collection
s simultanément, comme dans(A1, B1), (A2, B2), ...
. Cela nécessite une implémentation de "zip". Comment implémentez-vous "zip" si la fonction itération est implémentée sur l'un des deuxCollection
- comment accédez-vous à l'élément correspondant dans l'autre?Collection
doit s'implémenterzip
avec lui-même, selon la façon dont la bibliothèque est écrite. Si ce n'est pas le cas, vous l'implémentez et soumettez une demande d'extraction au créateur de la bibliothèque. Notez que j'ai convenu que certaines limites ont parfois besoin de getters, mon vrai problème est que les getters sont partout de nos jours.Collection
objet, au moins pour sa propre implémentation dezip
. (C'est-à-dire que le getter doit avoir au moins la visibilité du paquet.) Et maintenant, vousCollection
toute évidence, il peut accéder à son propre état interne, ou pour être plus précis, n'importe quelleCollection
instance peut accéder à l'état interne de n'importe quel autre. C'est le même type, pas besoin de getters.