L'encapsulation me dit de rendre tous ou presque tous les champs privés et de les exposer par des accesseurs / régleurs. Mais maintenant, des bibliothèques telles que Lombok apparaissent, ce qui nous permet d’exposer tous les champs privés par une courte annotation @Data
. Il créera des getters, des setters et des constructeurs de paramètres pour tous les domaines privés.
Quelqu'un pourrait-il m'expliquer quel est le sentiment de cacher tous les champs en tant que domaine privé et ensuite de les exposer tous avec une technologie supplémentaire? Pourquoi n'utilisons-nous tout simplement pas uniquement des champs publics? Je pense que nous avons parcouru un chemin long et difficile pour revenir au point de départ.
Oui, il existe d'autres technologies qui fonctionnent à travers des accesseurs et des passeurs. Et nous ne pouvons pas les utiliser à travers de simples champs publics. Mais ces technologies sont apparues uniquement parce que nous avions ces nombreuses propriétés - des champs privés derrière des accesseurs / poseurs publics. Si nous n'avions pas les propriétés, ces technologies se développeraient d'une autre manière et soutiendraient les champs publics. Et tout serait simple et nous n’aurions plus besoin de Lombok maintenant.
Quel est le sens de l'ensemble de ce cycle? Et l'encapsulation a-t-elle vraiment un sens maintenant dans la programmation de la vie réelle?
la source
"It will create getters, setters and setting constructors for all private fields."
- La façon dont vous décrivez cet outil, il semble que cela est le maintien de l' encapsulation. (Au moins dans un sens vague, automatisé et un peu anémique.) Alors quel est exactement le problème?Réponses:
Si vous exposez tous vos attributs avec des getters / setters, vous ne gagnez que la structure de données qui est toujours utilisée en C ou dans tout autre langage procédural. Ce n'est pas une encapsulation et Lombok fait en sorte que le code procédural soit moins pénible. Getters / setters aussi mauvais que de simples champs publics. Il n'y a pas de différence vraiment.
Et la structure de données n'est pas un objet. Si vous commencez à créer un objet à partir de l'écriture d'une interface, vous n'ajouterez jamais de getters / setters à l'interface. Exposer vos attributs conduit à un code procédural spaghetti dans lequel la manipulation de données se fait en dehors d’un objet et s’étend sur toute la base du code. Vous avez maintenant affaire à des données et à des manipulations au lieu de parler à des objets. Avec les getters / setters, vous aurez une programmation procédurale basée sur les données où la manipulation se fera de manière impérative. Obtenir des données - faire quelque chose - définir des données.
En POO, l'encapsulation est un éléphant si c'est fait correctement. Vous devez encapsuler les détails de l'état et de la mise en œuvre afin que l'objet ait le contrôle total sur cela. La logique sera focalisée à l'intérieur de l'objet et ne sera pas répandue dans la base de code. Et oui - l’encapsulation est toujours essentielle en programmation car le code sera plus facile à gérer.
EDITS
Après avoir vu les discussions en cours, je souhaite ajouter plusieurs choses:
Donc, si nous revenons à la question, pourquoi devrions-nous faire cela? Considérons cet exemple simple:
Ici, nous avons un document exposant les détails internes par getter et un code procédural externe
printDocument
fonctionnant en dehors de l'objet. Pourquoi est-ce mauvais? Parce que maintenant vous avez juste un code de style C. Oui, c'est structuré mais quelle est la différence? Vous pouvez structurer les fonctions C dans différents fichiers et avec des noms. Et ces soi-disant couches font exactement cela. La classe de service est simplement un ensemble de procédures qui fonctionnent avec des données. Ce code est moins facile à gérer et présente de nombreux inconvénients.Comparez avec celui-ci. Nous avons maintenant un contrat et les détails d'implémentation de ce contrat sont cachés à l' intérieur de l'objet. Maintenant, vous pouvez vraiment tester cette classe et cette classe encapsule certaines données. Comment cela fonctionne avec ces données est un objet concerne. Afin de parler avec l'objet maintenant, vous devez lui demander d'imprimer lui-même. C'est encapsulation et c'est un objet. Vous obtiendrez toute la puissance de l'injection de dépendance, des moqueries, des tests, des responsabilités uniques et de nombreux avantages avec la programmation orientée objet.
la source
Le sens est que vous n'êtes pas censé faire cela .
L'encapsulation signifie que vous n'exposez que les champs auxquels vous avez réellement besoin d'accéder à d'autres classes, et que vous êtes très sélectif et prudent.
Ne vous contentez pas de donner tous les champs getters et setters par défaut!
Cela va tout à fait à l’esprit de la spécification JavaBeans, qui est ironiquement d’où vient le concept de getters et setters publics.
Mais si vous regardez la spécification, vous verrez qu’elle avait l’intention de créer de manière très sélective la création de getters et de setters, et qu’elle parle de propriétés "en lecture seule" (pas de séparateur) et de propriétés getter).
Un autre facteur est que les accesseurs ne constituent pas nécessairement un simple accès au domaine privé . Un getter peut calculer la valeur qu'il retourne d'une manière arbitrairement complexe, peut-être la mettre en cache. Un intervenant peut valider la valeur ou notifier les écouteurs.
Donc, voilà: l’ encapsulation signifie que vous n’exposez que les fonctionnalités que vous devez réellement exposer. Mais si vous ne réfléchissez pas à ce que vous devez exposer et que vous exposez simplement, bon gré mal gré, en suivant une transformation syntaxique, alors bien sûr, ce n'est pas vraiment une encapsulation.
la source
getFieldName()
devient un contrat automatique pour nous. Nous ne nous attendons pas à un comportement complexe derrière cela. Dans la plupart des cas, il s’agit simplement de casser directement l’encapsulation.Je pense que le noeud de la question est expliqué par votre commentaire:
Le problème que vous avez est que vous mélangez le modèle de données de persistance avec le modèle de données actif .
Une application aura généralement plusieurs modèles de données:
en plus du modèle de données qu'il utilise réellement pour effectuer ses calculs.
En général, les modèles de données utilisés pour la communication avec l'extérieur doivent être isolés et indépendants du modèle de données interne (modèle d'objet métier, nomenclature) sur lequel les calculs sont effectués:
Dans ce scénario, il est tout à fait correct que les objets utilisés dans les couches de communication aient tous les éléments publics ou exposés par des accesseurs / régleurs. Ce sont des objets simples sans aucun invariant.
D'autre part, votre nomenclature doit avoir des invariants, ce qui empêche généralement d'avoir beaucoup de setters (les getters n'affectent pas les invariants, bien qu'ils réduisent l'encapsulation à un certain degré).
la source
Considérer ce qui suit..
Vous avez une
User
classe avec une propriétéint age
:Vous voulez augmenter cela afin d'
User
avoir une date de naissance, opposée à seulement un âge. À l'aide d'un getter:Nous pouvons échanger le
int age
terrain pour un plus complexeLocalDate dateOfBirth
:Pas de violation de contrat, pas de casse de code. Rien de plus que d’augmenter la représentation interne en prévision de comportements plus complexes.
Le champ lui-même est encapsulé.
Maintenant, pour effacer les préoccupations ..
L'
@Data
annotation de Lombok est similaire aux classes de données de Kotlin .Toutes les classes ne représentent pas des objets comportementaux. Pour ce qui est de la rupture de l'encapsulation, cela dépend de votre utilisation de la fonctionnalité. Vous ne devriez pas exposer tous vos champs via des accesseurs.
De manière plus générale, l’encapsulation est l’acte de cacher des informations. Si vous abusez
@Data
, il est facile de supposer que vous êtes probablement en train de casser l'encapsulation. Mais cela ne veut pas dire qu'il n'a pas de but. Les JavaBeans, par exemple, sont mal vus. Pourtant, il est largement utilisé dans le développement des entreprises.En concluriez-vous que le développement des entreprises est mauvais, à cause de l'utilisation de haricots? Bien sûr que non! Les exigences diffèrent de celles du développement standard. Peut-on abuser des haricots? Bien sûr! Ils sont maltraités tout le temps!
Lombok prend également en charge
@Getter
et@Setter
utilise indépendamment les besoins de vos besoins.la source
@Data
annotation sur un type qui, parsetAge(xyz)
Ce n'est pas ainsi que l'encapsulation est définie dans la programmation orientée objet. L’encapsulation signifie que chaque objet doit être semblable à une capsule dont la coque externe (l’API publique) protège et régule l’accès à son intérieur (méthodes et champs privés) et le cache à la vue. En cachant les internes, les appelants ne dépendent pas des internes, ce qui permet de modifier les internes sans modifier (ni même recompiler) les appelants. En outre, l’encapsulation permet à chaque objet d’appliquer ses propres invariants, en ne mettant à la disposition des appelants que des opérations sécurisées.
L'encapsulation est donc un cas particulier de masquage d'informations, dans lequel chaque objet masque ses éléments internes et applique ses invariants.
La génération de getters et de setters pour tous les champs est une forme d'encapsulation assez faible, car la structure des données internes n'est pas masquée et les invariants ne peuvent pas être appliqués. Cela présente l’avantage de pouvoir changer la façon dont les données sont stockées en interne (tant que vous pouvez convertir vers et depuis l’ancienne structure) sans avoir à changer (voire même à recompiler) les appelants.
En partie, cela est dû à un accident historique. En premier lieu, en Java, les expressions d’appel de méthode et les expressions d’accès aux champs sont syntaxiquement différentes sur le site d’appel; c’est-à-dire que remplacer un accès à un champ par un appel getter ou setter interrompt l’API d’une classe. Par conséquent, si vous avez besoin d’un accesseur, vous devez en écrire un maintenant ou pouvoir briser l’API. Cette absence de prise en charge des propriétés au niveau du langage contraste fortement avec les autres langages modernes, notamment C # et EcmaScript .
Strike 2 signifie que la spécification JavaBeans définissait les propriétés en tant que getters / setters, les champs n'étaient pas des propriétés. En conséquence, la plupart des premières infrastructures d'entreprise prenaient en charge les getters / setters, mais pas les champs. C’est déjà longtemps dans le passé ( API de persistance Java (JPA) , Validation de bean , Architecture Java pour la liaison XML (JAXB) , Jacksontous les champs d’aide sont maintenant satisfaisants), mais les anciens tutoriels et livres continuent de s’attarder, et tout le monde n’a pas conscience du fait que les choses ont changé. L’absence de prise en charge des propriétés au niveau de langue peut encore poser problème (par exemple, le chargement paresseux JPA d’une seule entité ne se déclenche pas lorsque les champs publics sont lus), mais la plupart des champs fonctionnent parfaitement. Pour résumer, ma société écrit tous les DTO pour leurs API REST avec des champs publics (après tout, le public ne transmet pas plus que ce qui est transmis sur Internet :-).
Cela dit, de Lombok
@Data
fait plus que générer des getters / setters: Il génère aussitoString()
,hashCode()
etequals(Object)
qui peut être très utile.L'encapsulation peut être inestimable ou totalement inutile, cela dépend de l'objet encapsulé. En règle générale, plus la logique de la classe est complexe, plus l'avantage de l'encapsulation est grand.
Les getters et les setters générés automatiquement pour chaque champ sont généralement surutilisés, mais ils peuvent être utiles pour travailler avec des frameworks existants ou pour utiliser la fonctionnalité de framework occasionnelle non prise en charge pour les champs.
L'encapsulation peut être réalisée à l'aide de méthodes d'accès et de commandes. Les setters ne sont généralement pas appropriés, car ils ne doivent modifier qu'un seul champ, alors que pour maintenir des invariants, il peut être nécessaire de modifier plusieurs champs à la fois.
Sommaire
les getters / setters offrent une encapsulation plutôt médiocre.
La prévalence de getters / setters en Java provient d'un manque de prise en charge des propriétés au niveau de la langue et de choix de conception douteux dans son modèle de composant historique, qui sont désormais inscrits dans de nombreux supports pédagogiques et les programmeurs qu'ils ont enseignés.
D'autres langages orientés objet, tels que EcmaScript, prennent en charge les propriétés au niveau du langage, de sorte que les getters peuvent être introduits sans casser l'API. Dans de tels langages, les accesseurs peuvent être introduits quand vous en avez réellement besoin, plutôt que d'avance, juste au cas où vous en auriez besoin un jour, ce qui rend l'expérience de programmation beaucoup plus agréable.
la source
Je me suis bien posé cette question.
Ce n'est pas tout à fait vrai cependant. La prévalence des getters / setters IMO est provoquée par la spécification Java Bean, qui l’exige; c'est donc à peu près une caractéristique de la programmation non orientée objet mais de la programmation orientée bean, si vous voulez. La différence entre les deux est celle de la couche d'abstraction dans laquelle ils existent; Les haricots sont davantage une interface système, c'est-à-dire une couche supérieure. Ils font abstraction du travail de base d’OO, ou sont destinés à au moins - comme toujours, les choses sont conduites trop souvent.
Je dirais qu'il est un peu regrettable que cette chose de Bean omniprésente dans la programmation Java ne soit pas accompagnée de l'ajout d'une fonctionnalité correspondante du langage Java - je pense à quelque chose comme le concept Properties en C #. Pour ceux qui ne le savent pas, c'est une construction de langage qui ressemble à ceci:
Quoi qu’il en soit, le strict minimum de la mise en œuvre réelle bénéficie toujours beaucoup de l’encapsulation.
la source
La réponse simple est la suivante: vous avez absolument raison. Les accesseurs et les setters éliminent la plupart (mais pas la totalité) de la valeur de l'encapsulation. Cela ne veut pas dire que chaque fois que vous avez une méthode get et / ou set que vous rompez l'encapsulation, mais si vous ajoutez aveuglément des accesseurs à tous les membres privés de votre classe, vous le faites mal.
Les Getters are setters sont omniprésents en programmation Java car le concept JavaBean a été poussé comme moyen de lier de manière dynamique les fonctionnalités au code prédéfini. Par exemple, vous pourriez avoir un formulaire dans une applet (vous en souvenez-vous?) Qui inspecterait votre objet, trouverait toutes les propriétés et afficherait les champs en tant que. Ensuite, l'interface utilisateur peut modifier ces propriétés en fonction des entrées de l'utilisateur. En tant que développeur, vous vous inquiétez alors de l’écriture de la classe et y placez toute validation ou logique métier, etc.
Utiliser des exemples de haricots
Ce n'est pas une idée terrible en soi mais je n'ai jamais été un grand fan de l'approche en Java. Cela va juste à contre-courant. Utilisez Python, Groovy, etc. Quelque chose qui supporte ce type d’approche de façon plus naturelle.
La chose JavaBean a été hors de contrôle parce qu'elle a créé JOBOL, c'est-à-dire des développeurs écrits en Java qui ne comprennent pas OO. Fondamentalement, les objets ne sont plus que des poches de données et toute la logique est écrite en dehors de longues méthodes. Parce que cela était considéré comme normal, les gens comme vous et moi qui le remettions en question étaient considérés comme des fous. Dernièrement, j'ai assisté à un changement, ce qui n'est pas vraiment une position extérieure
La liaison XML est un problème difficile. Ce n'est probablement pas un bon champ de bataille pour prendre position contre JavaBeans. Si vous devez créer ces JavaBeans, essayez de les garder en dehors du code réel. Traitez-les comme faisant partie de la couche de sérialisation.
la source
[Serializable]
attribut et ses parents. Pourquoi écrire 101 méthodes / classes de sérialisation spécifiques alors que vous pouvez simplement en écrire une en utilisant la réflexion?Combien pouvons-nous faire sans accesseurs? Est-il possible de les supprimer complètement? Quels problèmes cela crée-t-il? Peut-on même interdire le
return
mot clé?Il s'avère que vous pouvez faire beaucoup de choses si vous êtes prêt à faire le travail. Comment alors l'information peut-elle sortir de cet objet entièrement encapsulé? Par un collaborateur.
Plutôt que de laisser le code vous poser des questions, vous dites aux choses de faire les choses. Si ces choses ne retournent pas non plus, vous ne devez rien faire pour ce qu'elles retournent. Donc, lorsque vous songez à rechercher un
return
partenaire, essayez de rechercher un collaborateur de port de sortie qui fera tout ce qui reste à faire.Faire les choses de cette façon a des avantages et des conséquences. Vous devez penser à autre chose qu'à ce que vous auriez rendu. Vous devez penser à la manière dont vous allez envoyer cela comme un message à un objet qui ne vous l'a pas demandé. Il se peut que vous distribuiez le même objet que celui que vous auriez renvoyé ou que le simple fait d'appeler une méthode suffise. Faire cette réflexion a un coût.
L'avantage est que vous parlez maintenant face à l'interface. Cela signifie que vous obtenez le plein bénéfice de l'abstraction.
Cela vous donne également une distribution polymorphe, car si vous savez ce que vous dites, vous n'avez pas à savoir exactement ce que vous dites.
Vous pensez peut-être que cela signifie que vous devez parcourir une pile de couches dans un seul sens, mais il s'avère que vous pouvez utiliser le polymorphisme pour revenir en arrière sans créer de folles dépendances cycliques.
Cela pourrait ressembler à ceci:
Si vous pouvez coder comme ça, alors utiliser des accesseurs est un choix. Pas un mal nécessaire.
la source
Comme vous pouvez le constater, c’est une question controversée, car une série de dogmes et de malentendus est mêlée aux préoccupations raisonnables concernant la question des getters et des setters. Mais en bref, il n’ya rien de mal à
@Data
cela et cela ne rompt pas l’encapsulation.Pourquoi utiliser des getters et des setters plutôt que des champs publics?
Parce que les getters / setters assurent l’encapsulation. Si vous exposez une valeur en tant que champ public et que vous passez ensuite au calcul de la valeur à la volée, vous devez modifier tous les clients accédant au champ. Clairement c'est mauvais. Il s'agit d'un détail d'implémentation si une propriété d'un objet est stockée dans un champ, générée à la volée ou extraite ailleurs. La différence ne doit donc pas être exposée aux clients. Getter / setters setter résoudre ce problème, car ils cachent l'implémentation.
Mais si le getter / setter ne fait que refléter un champ privé sous-jacent, n'est-il pas aussi mauvais?
Non! Le fait est que l’encapsulation vous permet de modifier l’implémentation sans affecter les clients. Un champ peut toujours constituer un moyen parfaitement efficace de stocker de la valeur, à condition que les clients ne soient pas obligés de savoir ou d’être attentifs.
Mais l'auto-génération de getters / setters n'est-elle pas à partir de champs qui cassent l'encapsulation?
Non l'encapsulation est toujours là!
@Data
Les annotations ne sont qu'un moyen pratique pour écrire des paires getter / setter qui utilisent un champ sous-jacent. Pour le point de vue d'un client, cela ressemble à une paire de getter / setter normale. Si vous décidez de réécrire l'implémentation, vous pouvez toujours le faire sans affecter le client. Vous obtenez donc le meilleur des deux mondes: encapsulation et syntaxe concise.Mais certains disent que les getter / setters sont toujours mauvais!
Il existe une controverse distincte, selon laquelle certains pensent que le schéma d’obtention / définition est toujours mauvais, quelle que soit l’implémentation sous-jacente. L'idée est que vous ne devriez pas définir ou obtenir de valeurs d'objets, mais que toute interaction entre objets devrait être modélisée comme des messages lorsqu'un objet demande à un autre objet de faire quelque chose. C’est surtout un dogme des débuts de la pensée orientée objet. Nous pensons maintenant que pour certains modèles (par exemple, les objets de valeur, les objets de transfert de données), les getters / setters peuvent être parfaitement appropriés.
la source
L’encapsulation a bien un but, mais elle peut aussi être mal utilisée ou mal utilisée.
Considérons quelque chose comme l'API Android qui a des classes avec des dizaines (voire des centaines) de champs. Exposer ces champs au consommateur de l'API rend plus difficile la navigation et l'utilisation, mais donne également à l'utilisateur la fausse idée qu'il peut faire ce qu'il veut avec ces champs pouvant entrer en conflit avec la manière dont ils sont censés être utilisés. L'encapsulation est donc excellente dans ce sens pour la maintenabilité, la facilité d'utilisation, la lisibilité et pour éviter les bugs fous.
D'autre part, un type de données POD ou ancien, tel qu'une structure de C / C ++ dans laquelle tous les champs sont publics, peut également être utile. Avoir des getters / setters inutiles comme ceux générés par l'annotation @data à Lombok n'est qu'un moyen de conserver le "modèle d'encapsulation". Une des rares raisons pour lesquelles nous utilisons des getters / setters "inutiles" en Java est que les méthodes fournissent un contrat .
En Java, vous ne pouvez pas avoir de champs dans une interface. Par conséquent, vous utilisez des getters et des setters pour spécifier une propriété commune à tous les implémenteurs de cette interface. Dans des langages plus récents comme Kotlin ou C #, le concept de propriété est défini comme un champ pour lequel vous pouvez déclarer un séparateur et un getter. En fin de compte, les getters / setters inutiles sont plus un héritage avec lequel Java doit vivre, à moins que Oracle y ajoute des propriétés. Kotlin, par exemple, qui est un autre langage JVM développé par JetBrains, a des classes de données qui font comme l’annotation @data à Lombok.
Aussi, voici quelques exemples:
C'est un mauvais cas d'encapsulation. Le getter et le setter sont effectivement inutiles. L'encapsulation est principalement utilisée car il s'agit du standard dans des langages tels que Java. N'aide en réalité pas, outre le maintien de la cohérence dans la base de code.
C'est un bon exemple d'encapsulation. L'encapsulation est utilisée pour appliquer un contrat, dans ce cas IDataInterface. Le but de l'encapsulation dans cet exemple est de faire en sorte que le consommateur de cette classe utilise les méthodes fournies par l'interface. Bien que le getter et le setter ne fassent rien d'extraordinaire, nous avons maintenant défini un trait commun entre DataClass et d'autres implémenteurs d'IDataInterface. Ainsi, je peux avoir une méthode comme celle-ci:
Maintenant, quand on parle d’encapsulation, je pense qu’il est important de s’attaquer également au problème de syntaxe. Je vois souvent des gens se plaindre de la syntaxe nécessaire pour appliquer l'encapsulation plutôt que l'encapsulation elle-même. Un exemple qui me vient à l’esprit est celui de Casey Muratori (vous pouvez voir son discours ici ).
Supposons que vous ayez une classe de joueurs qui utilise l’encapsulation et que vous voulez déplacer sa position d’une unité. Le code ressemblerait à ceci:
Sans encapsulation, cela ressemblerait à ceci:
Il soutient ici que les encapsulations conduisent à beaucoup plus de dactylographie sans avantages supplémentaires et que cela peut dans de nombreux cas être vrai, mais remarquez quelque chose. L'argument est contraire à la syntaxe, pas à l'encapsulation elle-même. Même dans les langages comme le C qui n’ont pas le concept d’encapsulation, vous verrez souvent des variables dans des structures prexifed ou sufixées par '_' ou 'my' ou autre pour indiquer qu’elles ne doivent pas être utilisées par le consommateur de l’API, comme si elles étaient utilisées. privé.
Le fait est que l'encapsulation peut aider à rendre le code beaucoup plus facile à gérer et à utiliser. Considérez cette classe:
Si les variables étaient publiques dans cet exemple, le consommateur de cette API serait confus quant à savoir quand utiliser posX et posY et quand utiliser setPosition (). En masquant ces détails, vous aidez le consommateur à mieux utiliser votre API de manière intuitive.
La syntaxe est cependant une limitation dans de nombreuses langues. Cependant, les nouveaux langages offrent des propriétés qui nous donnent la syntaxe intéressante des membres de publice et les avantages de l'encapsulation. Vous trouverez des propriétés en C #, Kotlin, même en C ++ si vous utilisez MSVC. voici un exemple à Kotlin.
class VerticalList: ... {var posX: Int défini (x) {champ = x; ...} var posY: Int set (y) {champ = y; ...}}
Ici, nous avons réalisé la même chose que dans l'exemple Java, mais nous pouvons utiliser posX et posY comme s'il s'agissait de variables publiques. Quand j'essaye de changer leur valeur, le corps du set set () sera exécuté.
Dans Kotlin, par exemple, cela équivaudrait à un Java Bean avec getters, setters, hashcode, equals et toString:
Notez que cette syntaxe nous permet de créer un bean Java sur une seule ligne. Vous avez correctement remarqué le problème d’un langage tel que Java lors de l’implémentation de l’encapsulation, mais c’est la faute de Java et non de l’encapsulation elle-même.
Vous avez dit que vous utilisiez @Data de Lombok pour générer des getters et des setters. Notez le nom, @Data. Il est principalement conçu pour être utilisé sur des classes de données qui stockent uniquement des données et doivent être sérialisées et désérialisées. Pensez à quelque chose comme un fichier de sauvegarde d'un jeu. Mais dans d'autres scénarios, comme dans le cas d'un élément d'interface utilisateur, vous souhaitez sans aucun doute définir des paramètres, car modifier la valeur d'une variable risque de ne pas suffire pour obtenir le comportement attendu.
la source
L'encapsulation vous donne la flexibilité . En séparant structure et interface, cela vous permet de modifier la structure sans changer d'interface.
Par exemple, si vous constatez que vous devez calculer une propriété en fonction d'autres champs au lieu d'initialiser le champ sous-jacent lors de la construction, vous pouvez simplement changer le getter. Si vous aviez exposé le champ directement, vous devriez plutôt modifier l'interface et apporter des modifications sur chaque site d'utilisation.
la source
Je vais essayer d’illustrer l’espace problématique de l’encapsulation et de la conception de classe et de répondre à votre question à la fin.
Comme indiqué dans d'autres réponses, le but de l'encapsulation est de masquer les détails internes d'un objet derrière une API publique, qui sert de contrat. L'objet est sûr de changer ses éléments internes car il sait qu'il n'est appelé que par l'API publique.
Qu'il soit judicieux d'avoir des champs publics, des getters / setters, ou des méthodes de transaction de niveau supérieur ou la transmission de messages dépend de la nature du domaine modélisé. Dans le livre Akka Concurrency (que je peux recommander même s’il est un peu dépassé), vous trouverez un exemple illustrant cela, que je vais abréger ici.
Considérons une classe d'utilisateurs:
Cela fonctionne très bien dans un contexte à un seul thread. Le domaine en cours de modélisation est le nom d'une personne, et les mécanismes de la manière dont ce nom est stocké peuvent être parfaitement encapsulés par les setters.
Cependant, imaginez que cela doit être fourni dans un contexte multithread. Supposons qu'un thread lise périodiquement le nom:
Et deux autres discussions se livrent une lutte acharnée contre Hillary Clinton et Donald Trump . Ils ont chacun besoin d'appeler deux méthodes. Généralement, cela fonctionne bien, mais de temps en temps, vous allez voir passer Hillary Trump ou Donald Clinton .
Vous ne pouvez pas résoudre ce problème en ajoutant un verrou dans les paramètres, car celui-ci n’est maintenu que pendant la durée de la définition du prénom ou du nom de famille. La seule solution via le verrouillage consiste à ajouter un verrou autour de l'objet entier, mais cela casse l'encapsulation car le code appelant doit gérer le verrou (et peut provoquer des blocages).
Il s'avère qu'il n'y a pas de solution propre via le verrouillage. La solution propre consiste à encapsuler à nouveau les composants internes en les rendant plus grossiers:
Le nom lui-même est devenu immuable, et vous voyez que ses membres peuvent être publics, car il s'agit désormais d'un objet de données pur, sans possibilité de le modifier une fois créé. À son tour, l'API publique de la classe User est devenue plus grossière, avec un seul séparateur à gauche, de sorte que le nom ne peut être modifié que dans son ensemble. Il encapsule plus de son état interne derrière l'API.
Ce que vous voyez dans ce cycle, ce sont des tentatives pour appliquer des solutions trop bonnes pour un ensemble spécifique de circonstances. Un niveau approprié d’encapsulation nécessite de comprendre le domaine modélisé et d’appliquer le bon niveau d’encapsulation. Parfois, cela signifie que tous les champs sont publics, parfois (comme dans les applications Akka), cela signifie que vous n'avez aucune API publique, à l'exception d'une seule méthode pour recevoir des messages. Cependant, le concept même d’encapsulation, c’est-à-dire le masquage des éléments internes derrière une API stable, est essentiel pour la programmation de logiciels à grande échelle, en particulier dans les systèmes multithreads.
la source
Je peux penser à un cas d'utilisation où cela a du sens. Vous pouvez avoir une classe à laquelle vous accédez à l'origine via une simple API getter / setter. Vous étendez ou modifiez ultérieurement pour qu'il n'utilise plus les mêmes champs, mais qu'il supporte toujours la même API .
Un exemple quelque peu artificiel: un point qui commence par une paire cartésienne avec
p.x()
etp.y()
. Vous créez ensuite une nouvelle implémentation ou une sous-classe qui utilise les coordonnées polaires. Vous pouvez donc également appelerp.r()
etp.theta()
, mais votre code client appellep.x()
etp.y()
reste valide. La classe elle-même convertit de manière transparente à partir de la forme polaire interne, c’est-à-dire ley()
ferait maintenantreturn r * sin(theta);
. (Dans cet exemple, définir seulementx()
ouy()
n'a pas beaucoup de sens, mais cela reste possible.)Dans ce cas, vous pourriez vous retrouver en train de vous dire: «Je suis heureux d'avoir déclaré automatiquement des accesseurs et des passeurs au lieu de rendre les champs publics, sinon j'aurais dû casser mon API."
la source
Il n'y a absolument aucun point. Cependant, le fait que vous posiez cette question montre que vous n'avez pas compris ce que Lombok fait et que vous ne comprenez pas comment écrire du code OO avec une encapsulation. Rembobinons un peu ...
Certaines données d'une instance de classe seront toujours internes et ne devraient jamais être exposées. Certaines données pour une instance de classe devront être définies en externe et certaines données devront peut-être être renvoyées d'une instance de classe. Nous voudrons peut-être modifier le fonctionnement de la classe sous la surface, nous utilisons donc des fonctions pour nous permettre d’obtenir et de définir des données.
Certains programmes veulent sauvegarder l'état pour les instances de classe, elles peuvent donc avoir une interface de sérialisation. Nous ajoutons d'autres fonctions qui permettent à l'instance de classe de stocker son état dans le stockage et de récupérer son état dans le stockage. Cela maintient l'encapsulation car l'instance de la classe contrôle toujours ses propres données. Nous sommes peut-être en train de sérialiser des données privées, mais le reste du programme n'y a pas accès (ou plus exactement, nous maintenons un mur chinois en choisissant de ne pas corrompre délibérément ces données privées), et l'instance de classe peut (et devrait) effectuer des contrôles d’intégrité sur la désérialisation pour s’assurer que ses données sont bien retournées.
Parfois, les données nécessitent des vérifications de plage, des vérifications d’intégrité, etc. L'écriture de ces fonctions nous permet de faire tout cela. Dans ce cas, nous ne voulons ni n’avons besoin de Lombok, car nous faisons tout cela nous-mêmes.
Fréquemment, vous trouvez qu'un paramètre défini en externe est stocké dans une seule variable. Dans ce cas, vous aurez besoin de quatre fonctions pour obtenir / définir / sérialiser / désérialiser le contenu de cette variable. L'écriture de ces quatre fonctions vous ralentit à chaque fois et est source d'erreurs. L'automatisation du processus avec Lombok accélère votre développement et supprime les risques d'erreur.
Oui, il serait possible de rendre cette variable publique. Dans cette version particulière du code, il serait fonctionnellement identique. Mais revenons à la raison pour laquelle nous utilisons des fonctions: "Nous pouvons vouloir changer la façon dont la classe traite les choses sous la surface ..." Si vous rendez votre variable publique, vous contraignez votre code maintenant et pour toujours à avoir cette variable publique en tant que L'interface. Si vous utilisez des fonctions ou si vous utilisez Lombok pour générer automatiquement ces fonctions pour vous, vous êtes libre de modifier les données sous-jacentes et l'implémentation sous-jacente à tout moment.
Est-ce que cela rend les choses plus claires?
la source
Je ne suis pas réellement un développeur Java; mais ce qui suit est à peu près agnostique.
À peu près tout ce que nous écrivons utilise des getters et des setters publics qui accèdent à des variables privées. La plupart des getters et setters sont triviaux. Mais lorsque nous décidons que le configurateur doit recalculer quelque chose ou qu'il effectue une validation ou que nous devons transférer la propriété à une propriété d'une variable membre de cette classe, cela ne perturbe en rien le code entier et est compatible binaire. peut échanger ce module sur.
Lorsque nous décidons que cette propriété doit vraiment être calculée à la volée, tout le code qui la regarde n’a pas à changer et seul le code qui l’écrit doit changer et l’EDI peut la trouver pour nous. Lorsque nous décidons qu'il s'agit d'un champ calculé accessible en écriture (nous ne devons le faire que quelques fois), nous pouvons également le faire. La bonne chose est que bon nombre de ces modifications sont compatibles avec les binaires (le passage au champ calculé en lecture seule n’est pas théorique, mais pourrait être pratique dans tous les cas).
Nous nous sommes retrouvés avec un tas de getters triviaux avec des setters compliqués. Nous nous sommes également retrouvés avec plusieurs getters de cache. Le résultat final est que vous êtes autorisé à supposer que les getters sont raisonnablement bon marché, mais que les setters peuvent ne pas l'être. D'autre part, nous sommes assez sages pour décider que les setters ne persistent pas sur le disque.
Mais je devais retrouver le type qui changeait aveuglément toutes les variables membres en propriétés. Il ne savait pas ce que c'était que l'ajout atomique, il a donc changé la chose qui devait être une variable publique en une propriété et brisé le code de manière subtile.
la source
Les accesseurs sont une mesure "au cas où" ajoutée afin d'éviter le refactoring à l'avenir si la structure interne ou les conditions d'accès changent pendant le processus de développement.
Par exemple, quelques mois après la publication, votre client vous informe que l’un des champs d’une classe est parfois défini sur des valeurs négatives, même s’il doit rester au maximum égal à 0 dans ce cas. En utilisant des champs publics, vous devez rechercher chaque affectation de ce champ dans l'ensemble de votre base de code pour appliquer la fonction de blocage à la valeur que vous allez définir, et gardez à l'esprit que vous devrez toujours le faire lorsque vous modifierez ce champ, mais ça craint. Au lieu de cela, si vous utilisiez déjà des accesseurs et des passeurs, vous n'auriez pu que modifier votre méthode setField () pour que ce blocage soit toujours appliqué.
Maintenant, le problème avec les langages "obsolètes" comme Java, c'est qu'ils incitent à utiliser des méthodes à cette fin, ce qui rend votre code infiniment plus détaillé. C'est pénible à écrire et difficile à lire, c'est pourquoi nous utilisons IDE pour atténuer ce problème d'une manière ou d'une autre. La plupart des IDE génèreront automatiquement pour vous des accesseurs et des setters, et les cacheront également, sauf indication contraire. Lombok va encore plus loin et les génère simplement de manière procédurale pendant la compilation pour que votre code soit extrêmement élégant. Cependant, d'autres langues plus modernes ont simplement résolu ce problème d'une manière ou d'une autre. Langages Scala ou .NET, par exemple,
Par exemple, dans VB .NET ou C #, vous pouvez simplement faire en sorte que tous les champs soient conçus pour avoir des getters et des setters secondaires simples et non définitifs, puis les rendre privés, changer leur nom et exposer une propriété avec le nom précédent de le champ, où vous pouvez ajuster le comportement d'accès du champ, si vous en avez besoin. Avec Lombok, si vous avez besoin de peaufiner le comportement d'un getter ou d'un setter, vous pouvez simplement supprimer ces balises si nécessaire et coder les vôtres avec les nouvelles exigences, tout en sachant que vous ne devrez rien modifier dans les autres fichiers.
Fondamentalement, la manière dont votre méthode accède à un champ doit être transparente et uniforme. Les langages modernes vous permettent de définir des "méthodes" avec la même syntaxe d'accès / d'appel d'un champ. Ces modifications peuvent donc être effectuées à la demande sans trop y penser au tout début du développement, mais Java vous oblige à effectuer ce travail à l'avance, car il le fait. pas cette fonctionnalité. Tout ce que Lombok fait, c'est vous faire gagner du temps, car la langue que vous utilisez ne voulait pas vous laisser gagner du temps pour une méthode "au cas où".
la source
foo.bar
mais elle peut être gérée par un appel de méthode. Vous affirmez que cela est supérieur à la méthode "dépassée" qui consiste à faire en sorte qu'une API ressemble à des appels de méthodefoo.getBar()
. Nous semblons d’accord pour dire que les champs publics sont problématiques, mais j’affirme que l’alternative "dépassée" est supérieure à la variante "moderne", dans la mesure où notre API est symétrique (ce sont tous des appels de méthodes). Dans l'approche "moderne", nous devons décider quelles choses doivent être des propriétés et lesquelles doivent être des méthodes, ce qui complique tout (tout particulièrement si nous utilisons la réflexion!).Oui, c'est contradictoire. J'ai d'abord rencontré des propriétés dans Visual Basic. D'après mon expérience, il n'existait jusqu'alors pas de wrappers de propriétés autour des champs. Juste des champs publics, privés et protégés.
Les propriétés étaient une sorte d'encapsulation. J'ai compris que les propriétés Visual Basic constituaient un moyen de contrôler et de manipuler la sortie d'un ou plusieurs champs tout en masquant le champ explicite et même son type de données, par exemple en émettant une date sous forme de chaîne dans un format particulier. Mais même dans ce cas, il ne faut pas "masquer l'état et exposer la fonctionnalité" du point de vue de l'objet.
Mais les propriétés se justifient par des accessions et des propriétés distinctes. Exposer un champ sans propriété était tout ou rien - si vous pouviez le lire, vous pourriez le changer. Alors maintenant, on peut justifier une conception de classe faible avec des setters protégés.
Alors pourquoi n’avons-nous pas / ont-ils simplement utilisé des méthodes réelles? Parce que c’était Visual Basic (et VBScript ) (oooh! Aaah!), Codant pour les masses (!), Et c’était à la mode. Et ainsi une idiotocratie a fini par dominer.
la source