web-dev-qa-db-fra.com

L'encapsulation est-elle toujours l'un des éléphants OOP se tient debout?

L'encapsulation me dit de rendre tous ou presque tous les champs privés et de les exposer par des getters/setters. Mais maintenant des bibliothèques telles que Lombok apparaissent qui nous permettent d'exposer tous les champs privés par une courte annotation @Data. Il créera des getters, des setters et des constructeurs de configuration pour tous les domaines privés.

Quelqu'un pourrait-il m'expliquer à quoi sert de cacher tous les champs comme privés et ensuite de les exposer tous par une technologie supplémentaire? Pourquoi n'utilisons-nous donc tout simplement pas uniquement les champs publics? Je pense que nous avons parcouru un long et difficile chemin pour revenir au point de départ.

Oui, il existe d'autres technologies qui fonctionnent grâce aux getters et setters. 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 getters/setters publics. Si nous n'avions pas les propriétés, ces technologies se développeraient autrement et prendraient en charge les domaines 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 dans la programmation réelle?

99
Gangnus

Si vous exposez tous vos attributs avec des getters/setters, vous obtenez simplement une structure de données qui est toujours utilisée en C ou dans tout autre langage procédural. C'est pas l'encapsulation et Lombok fait juste travailler avec du code procédural moins douloureux. Getters/setters aussi mauvais que les champs publics ordinaires. Il n'y a pas vraiment de différence.

Et la structure des 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. L'exposition de vos attributs conduit à du code procédural spaghetti où la manipulation des données est en dehors d'un objet et se propage dans la base de code. Vous vous occupez maintenant de données et de manipulations de données 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 avec cela se fera de manière impérative. Obtenez des données - faites quelque chose - définissez des données.

In OOP encapsulation is a elephant if done in right way. You should encapsulate state and implementation details so that object has full control on that. La logique sera focalisée à l'intérieur de l'objet et ne sera pas répartie partout Et oui - l'encapsulation est toujours essentielle dans la programmation car le code sera plus facile à maintenir.

ÉDITE

Après avoir vu les discussions en cours, je veux ajouter plusieurs choses:

  • Peu importe le nombre de vos attributs que vous exposez à travers des getters/setters et à quel point vous faites cela. Être plus sélectif ne rendra pas votre code OOP avec encapsulation. Chaque attribut que vous exposez conduira à une procédure de travail avec ces données nues de manière procédurale impérative. Vous diffuserez simplement votre code moins lentement en étant plus sélectif. Cela ne change pas le cœur.
  • Oui, dans les limites du système, vous obtenez des données nues d'autres systèmes ou bases de données. Mais ces données ne sont qu'un autre point d'encapsulation.
  • Les objets doivent être fiable. L'idée entière des objets est d'être responsable de sorte que vous n'ayez pas besoin de donner des ordres qui soient droits et impératifs. Au lieu de cela vous demandez à un objet de faire ce qu'il fait bien à travers le contrat. Vous déléguez en toute sécurité la partie agissante à l'objet . Objets encapsuler détails sur l'état et l'implémentation.

Donc, si nous revenons à la question de savoir pourquoi nous devrions le faire. Considérez cet exemple simple:

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

Ici, nous avons Document exposant les détails internes par getter et avons un code procédural externe dans la fonction printDocument qui fonctionne avec cela à l'extérieur 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 vraiment 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 n'est qu'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.

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

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. La façon dont cela fonctionne avec ces données est un sujet de préoccupation. Pour parler avec un objet, vous devez maintenant lui demander d'imprimer lui-même. C'est de l'encapsulation et c'est un objet. Vous obtiendrez toute la puissance de l'injection de dépendances, de la moquerie, des tests, des responsabilités uniques et de nombreux avantages avec la POO.

84
Izbassar Tolegen

Quelqu'un pourrait-il m'expliquer, quel est le sens de cacher tous les champs comme privés et ensuite de les exposer tous par une technologie supplémentaire? Pourquoi n'utilisons-nous donc pas uniquement les champs publics?

Le sens est que vous n'êtes pas censé le faire.

L'encapsulation signifie que vous exposez uniquement les champs auxquels vous avez réellement besoin d'autres classes pour accéder, et que vous êtes très sélectif et prudent à ce sujet.

Ne pas donnez simplement tous les champs getters et setters par défaut!

Cela va complètement à l'encontre de 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 voulait que la création de getters et setters soit très sélective, et qu'elle parle de propriétés "en lecture seule" (pas de setter) et de propriétés "en écriture seule" (non getter).

Un autre facteur est que les getters et setters ne sont pas nécessairement un simple accès au champ privé. Un getter peut calculer la valeur qu'il renvoie d'une manière arbitrairement complexe, peut-être le mettre en cache. Un setter peut valider la valeur ou avertir les auditeurs.

Donc, vous êtes là: l'encapsulation signifie que vous exposez uniquement les fonctionnalités que vous devez réellement exposer. Mais si vous ne pensez pas à ce que vous devez exposer et que vous exposez tout bon gré mal gré en suivant certaines syntaxiques transformation alors bien sûr ce n'est pas vraiment de l'encapsulation.

76

Je pense que le nœud du problème est expliqué par votre commentaire:

Je suis totalement d'accord avec votre pensée. Mais quelque part, nous devons charger des objets avec des données. Par exemple, à partir de XML. Et les plates-formes actuelles qui le prennent en charge le font via des getters/setters, dégradant ainsi la qualité de notre code et notre façon de penser. Lombok n'est vraiment pas mauvais en soi, mais son existence même montre que nous avons quelque chose de mauvais.

Le problème que vous avez est que vous mélangez le modèle de données de persistance avec le actif modèle de données.

Une application aura généralement plusieurs modèles de données :

  • un modèle de données pour parler à la base de données,
  • un modèle de données pour lire le fichier de configuration,
  • un modèle de données pour parler à une autre application,
  • ...

au-dessus 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 à partir du modèle de données interne (modèle objet métier, BOM) sur lequel les calculs sont effectués:

  • indépendant: pour que vous puissiez ajouter/supprimer des propriétés sur la nomenclature selon vos besoins sans avoir à changer tous les clients/serveurs, ...
  • isolé: pour que tous les calculs soient effectués sur la nomenclature, où vivent les invariants, et que le passage d'un service à un autre, ou la mise à niveau d'un service, ne provoque pas une ondulation dans la base de code.

Dans ce scénario, il est parfaitement correct que les objets utilisés dans les couches de communication aient tous les éléments publics ou exposés par des getters/setters. 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 dans une certaine mesure).

18
Matthieu M.

Considérer ce qui suit..

Vous avez une classe User avec une propriété int age:

class User {
    int age;
}

Vous voulez faire évoluer cela afin qu'un User ait une date de naissance, par opposition à seulement un âge. À l'aide d'un getter:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Nous pouvons remplacer le champ int age Par un LocalDate dateOfBirth Plus complexe:

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of dateOfBirth
}

Pas de violation de contrat, pas de rupture de code. Rien de plus que l'extension de la représentation interne en préparation à des comportements plus complexes.

Le champ lui-même est encapsulé.


Maintenant, pour effacer les préoccupations ..

L'annotation @Data De Lombok est similaire à celle de Kotlin classes de données .

Toutes les classes ne représentent pas des objets comportementaux. Quant à la rupture de l'encapsulation, cela dépend de votre utilisation de la fonctionnalité. Vous ne devez pas exposer tous vos champs via des getters.

L'encapsulation est utilisée pour masquer les valeurs ou l'état d'un objet de données structuré à l'intérieur d'une classe

Dans un sens plus général, l'encapsulation est l'acte de cacher des informations. Si vous abusez de @Data, Il est facile de supposer que vous brisez probablement l'encapsulation. Mais cela ne veut pas dire que cela n'a aucun but. Les JavaBeans, par exemple, sont mal vus par certains. Pourtant, il est largement utilisé dans le développement des entreprises.

Diriez-vous que le développement d'une entreprise est mauvais en raison de l'utilisation de beans? 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 abusés tout le temps!

Lombok prend également en charge @Getter Et @Setter Indépendamment - utilisez ce que vos exigences exigent.

12
Vince Emigh

L'encapsulation me dit de rendre tous ou presque tous les champs privés et de les exposer par des getters/setters.

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 comme une capsule, dont la coque extérieure (l'api publique) protège et régule l'accès à son intérieur (les méthodes et champs privés), et le cache à la vue. En masquant les internes, les appelants ne dépendent pas des internes, ce qui permet de modifier les internes sans changer (ni même recompiler) les appelants. De plus, l'encapsulation permet à chaque objet d'appliquer ses propres invariants, en ne mettant à la disposition des appelants que des opérations sûres.

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 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 a 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 (ou même recompiler) les appelants, cependant.

Quelqu'un pourrait-il m'expliquer à quoi sert de cacher tous les champs comme privés et ensuite de les exposer tous par une technologie supplémentaire? Pourquoi n'utilisons-nous donc tout simplement pas uniquement les champs publics? Je pense que nous avons parcouru un long et difficile chemin pour revenir au point de départ.

Cela est dû en partie à un accident historique. La première est que, 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 le remplacement d'un accès de champ par un appel getter ou setter rompt l'API d'une classe. Par conséquent, si vous avez besoin d'un accesseur, vous devez en écrire un maintenant, ou être capable de casser 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 .

La grève 2 est que les JavaBeansspécifications propriétés définies comme getters/setters, les champs n'étaient pas des propriétés. Par conséquent, la plupart des premiers cadres d'entreprise prenaient en charge les getters/setters, mais pas les champs. C'est long dans le passé maintenant ( Java Persistence API (JPA) , Bean Validation , Java Architecture for XML Binding (JAXB) , - Jackson tous les champs de support sont maintenant très bien), mais les anciens tutoriels et livres persistent, et tout le monde n'est pas conscient que les choses ont changé. L'absence de prise en charge des propriétés au niveau de la langue peut toujours être difficile dans certains cas (par exemple, car le chargement paresseux JPA d'entités uniques ne se déclenche pas lorsque les champs publics sont lus), mais la plupart du temps les champs publics fonctionnent très bien. À savoir, mon entreprise écrit tous les DTO pour leurs REST avec des champs publics (il n'y a pas plus de public qui soit transmis sur Internet, après tout :-)).

Cela dit, @Data De Lombok fait plus que générer des getters/setters: il génère également toString(), hashCode() et equals(Object), ce qui peut être très utile .

Et l'encapsulation a-t-elle vraiment un sens maintenant dans la programmation réelle?

L'encapsulation peut être inestimable ou tout à fait inutile, cela dépend de l'objet encapsulé. Généralement, plus la logique au sein de la classe est complexe, plus l'avantage de l'encapsulation est grand.

Les getters et setters générés automatiquement pour chaque champ sont généralement surutilisés, mais peuvent être utiles pour travailler avec des frameworks hérités, ou pour utiliser la fonctionnalité de framework occasionnelle non prise en charge pour les champs.

L'encapsulation peut être réalisée avec des getters et des méthodes de commande. Les décanteurs ne sont généralement pas appropriés, car ils ne devraient modifier qu'un seul champ, tandis que le maintien des invariants peut nécessiter la modification de plusieurs champs à la fois.

Résumé

les getters/setters offrent une encapsulation plutôt médiocre.

La prévalence des getters/setters en Java découle d'un manque de prise en charge du niveau de langue pour les propriétés et de choix de conception douteux dans son modèle de composants historiques qui ont été inscrits dans de nombreux supports pédagogiques et les programmeurs enseignés par leur.

D'autres langages orientés objet, comme 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 getters peuvent être introduits lorsque vous en avez réellement besoin, plutôt qu'à l'avance, juste au cas où vous en auriez besoin un jour, ce qui rend l'expérience de programmation beaucoup plus agréable.

11
meriton

Je me suis effectivement posé cette question.

Ce n'est pas tout à fait vrai cependant. La prévalence des getters/setters IMO est causé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 se trouve celle de la couche d'abstraction dans laquelle ils existent; les Beans sont plus une interface système, c'est-à-dire sur une couche supérieure. Ils font abstraction du OO travail de base, ou sont destinés à au moins - comme toujours, les choses vont trop souvent trop loin.

Je dirais qu'il est quelque peu malheureux que cette chose Bean qui soit omniprésente dans la programmation Java ne soit pas accompagnée de l'ajout d'une fonction de langage Java Java - I ' Je pense à quelque chose comme le concept Propriétés en C #. Pour ceux qui ne le connaissent pas, c'est une construction de langage qui ressemble à ceci:

class MyClass {
    string MyProperty { get; set; }
}

Quoi qu'il en soit, l'essentiel de la mise en œuvre réelle profite toujours beaucoup de l'encapsulation.

7
daniu

Quelqu'un pourrait-il m'expliquer, quel est le sens de cacher tous les champs comme privés et ensuite de les exposer tous par une technologie supplémentaire? Pourquoi n'utilisons-nous donc pas uniquement les champs publics? Je pense que nous avons parcouru un chemin long et difficile pour revenir au point de départ.

La réponse simple ici est: vous avez absolument raison. Les getters et 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 cassez l'encapsulation, mais si vous ajoutez aveuglément des accesseurs à tous les membres privés de votre classe, vous vous trompez.

Oui, il existe d'autres technologies qui fonctionnent grâce aux getters et setters. 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 getters/setters publics. Si nous n'avions pas les propriétés, ces technologies se développeraient autrement et prendraient en charge les domaines 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 réelle?

Les getters sont des setters omniprésents dans la programmation Java parce que le concept JavaBean a été poussé comme un moyen de lier dynamiquement des fonctionnalités à du code prédéfini. Par exemple, vous pouvez avoir un formulaire dans une applet (n'importe qui souvenez-vous de ceux?) qui inspecteraient votre objet, trouveraient toutes les propriétés et afficheraient les champs en tant que. L'interface utilisateur peut alors modifier ces propriétés en fonction de la saisie de l'utilisateur. là etc.

enter image description here

tilisation d'exemples de beans

Ce n'est pas une idée terrible en soi, mais je n'ai jamais été un grand fan de l'approche en Java. Ça va à contre-courant. Utilisez Python, Groovy etc. Quelque chose qui prend en charge ce type d'approche plus naturellement.

La chose JavaBean est devenue incontrôlable car elle a créé JOBOL, c'est-à-dire Java développeurs écrits qui ne comprennent pas OO. Fondamentalement, les objets ne sont devenus que des sacs de données et toute la logique a été écrite à l'extérieur dans les méthodes longues. Parce que c'était considéré comme normal, des gens comme vous et moi qui remettons cela en question étaient considérés comme des kooks. Dernièrement, j'ai vu un changement et ce n'est pas autant une position d'outsider

La liaison XML est un écrou difficile. Ce n'est probablement pas un bon champ de bataille pour prendre position contre JavaBeans. Si vous devez construire ces JavaBeans, essayez de les garder hors du vrai code. Traitez-les comme faisant partie de la couche de sérialisation.

6
JimmyJames

C'est une question controversée (comme vous pouvez le voir) car un tas de dogmes et de malentendus se mêle aux préoccupations raisonnables concernant la question des getters et des setters. Mais en bref, il n'y a rien de mal à @Data et il ne rompt pas l'encapsulation.

Pourquoi utiliser des getters et setters plutôt que des champs publics?

Parce que les getters/setters fournissent l'encapsulation. Si vous exposez une valeur en tant que champ public, puis modifiez ultérieurement le calcul de la valeur à la volée, vous devez modifier tous les clients accédant au champ. C'est clairement mauvais. C'est un détail d'implémentation si une propriété d'un objet est stockée dans un champ, est générée à la volée ou est récupérée ailleurs, donc la différence ne doit pas être exposée aux clients. Les getter/setters setter résolvent ce problème, car ils masquent l'implémentation.

Mais si le getter/setter reflète simplement un champ privé sous-jacent, n'est-ce pas tout aussi mauvais?

Non! Le fait est que l'encapsulation vous permet de modifier l'implémentation sans affecter les clients. Un champ peut toujours être un excellent moyen de stocker de la valeur, tant que les clients n'ont pas besoin de savoir ou de se soucier.

Mais les getters/setters à génération automatique des champs ne cassent-ils pas l'encapsulation?

Non, l'encapsulation est toujours là! @Data les annotations ne sont qu'un moyen pratique d'écrire des paires getter/setter qui utilisent un champ sous-jacent. Pour le point de vue d'un client, c'est comme une paire getter/setter ordinaire. 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, où certains pensent que le modèle getter/setter est toujours mauvais, indépendamment de l'implémentation sous-jacente. L'idée est que vous ne devez pas définir ou obtenir de valeurs à partir d'objets, plutôt que toute interaction entre les objets doit être modélisée comme des messages où un objet demande un autre objet à faire quelque chose. Il s'agit principalement d'un dogme des premiers jours de la pensée orientée objet. L'idée est maintenant que pour certains modèles (par exemple, objets de valeur, objet de transfert de données), les getters/setters peuvent être parfaitement appropriés.

5
JacquesB

Que pouvons-nous faire sans getters? Est-il possible de les supprimer complètement? Quels problèmes cela crée-t-il? Pouvons-nous même interdire le mot clé return?

Il s'avère que vous pouvez faire beaucoup si vous êtes prêt à faire le travail. Comment alors les informations peuvent-elles sortir de cet objet entièrement encapsulé? Par l'intermédiaire d'un collaborateur.

Plutôt que de laisser le code vous poser des questions, vous dites des choses à faire. Si ces choses ne vous retournent pas non plus, vous n'avez rien à faire de ce qu'elles retournent. Donc, lorsque vous envisagez de rechercher un return, essayez plutôt 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 à plus que ce que vous seriez retourné. Vous devez penser à la façon dont vous allez envoyer cela comme un message à un objet qui ne l'a pas demandé. Il se peut que vous passiez le même objet que vous auriez retourné, ou qu'il suffise d'appeler une méthode suffit. Faire cette réflexion a un coût.

L'avantage est que maintenant vous parlez face à l'interface. Cela signifie que vous profitez pleinement de l'abstraction.

Cela vous donne également une répartition polymorphe, car si vous savez ce que vous dites, vous n'avez pas besoin de savoir exactement à quoi vous le dites.

Vous pourriez penser que cela signifie que vous ne devez parcourir qu'un seul chemin à travers une pile de couches, mais il s'avère que vous pouvez utiliser le polymorphisme pour revenir en arrière sans créer de dépendances cycliques folles.

Cela pourrait ressembler à ceci:

Enter image description here

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

Si vous pouvez coder comme ça, utiliser des getters est un choix. Pas un mal nécessaire.

5
candied_orange

L'encapsulation a un but, mais elle peut également être utilisée à mauvais escient ou abusée.

Considérez quelque chose comme l'API Android Android qui a des classes avec des dizaines (sinon des centaines) de champs. L'exposition de ces champs au consommateur de l'API rend la navigation et l'utilisation plus difficiles, et donne également à l'utilisateur la fausse idée qu'il peut faire tout ce qu'il veut avec ces champs qui peuvent entrer en conflit avec la façon dont ils sont censés être utilisés. L'encapsulation est donc excellente dans ce sens pour la maintenabilité, l'utilisabilité, la lisibilité et éviter les bugs fous.

D'un autre côté, le POD ou les anciens types de données simples, comme une structure de C/C++ dans laquelle tous les champs sont publics, peuvent également être utiles. Avoir des getters/setters inutiles comme ceux générés par l'annotation @data dans Lombok est juste un moyen de garder le "modèle d'encapsulation". L'une des rares raisons pour lesquelles nous faisons des getters/setters "inutiles" dans Java est que les méthodes fournissent un contrat .

En Java, vous ne pouvez pas avoir de champs dans une interface, vous utilisez donc des getters et 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 #, nous voyons le concept de propriétés comme des champs pour lesquels vous pouvez déclarer un setter et un getter. En fin de compte, les getters/setters inutiles sont plus un héritage avec lequel Java doit vivre, à moins qu'Oracle ne lui 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 essentiellement ce que fait l'annotation @data dans Lombok.

Voici également quelques exemples:

class DataClass 
{
    private int data;

    public int getData() { return data; }
    public void setData(int data) { this.data = data; } 
}

Il s'agit d'un mauvais cas d'encapsulation. Le getter et le setter sont effectivement inutiles. L'encapsulation est principalement utilisée car c'est la norme dans des langages comme Java. N'aide pas vraiment, en plus de maintenir la cohérence à travers la base de code.

class DataClass implements IDataInterface
{
    private int data;

    @Override public int getData() { return data; }
    @Override public void setData(int data) { this.data = data; }
}

Ceci 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. Même si le getter et le setter ne font 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:

void doSomethingWithData(IDataInterface data) { data.setData(...); }

Maintenant, quand je parle d'encapsulation, je pense qu'il est important d'aborder également le problème de syntaxe. Je vois souvent des gens se plaindre de la syntaxe qui est nécessaire pour appliquer l'encapsulation plutôt que l'encapsulation elle-même. Un exemple qui me vient à l'esprit vient de Casey Muratori (vous pouvez voir sa diatribe ici ).

Supposons que vous ayez une classe de joueur qui utilise l'encapsulation et que vous souhaitiez déplacer sa position d'une unité. Le code ressemblerait à ceci:

player.setPosX(player.getPosX() + 1);

Sans encapsulation, cela ressemblerait à ceci:

player.posX++;

Ici, il soutient que les encapsulations conduisent à beaucoup plus de frappe sans avantages supplémentaires et cela peut dans de nombreux cas être vrai, mais remarquez quelque chose. L'argument est contre la syntaxe, pas l'encapsulation elle-même. Même dans des langages comme C qui n'ont pas le concept d'encapsulation, vous verrez souvent des variables dans des structures prédéfinies ou suffixées avec '_' ou 'mon' ou autre pour signifier qu'elles ne devraient pas être utilisées par le consommateur de l'API, comme si elles étaient privé.

Le fait est que l'encapsulation peut aider à rendre le code beaucoup plus facile à gérer et à utiliser. Considérez cette classe:

class VerticalList implements ...
{
    private int posX;
    private int posY;
    ... //other members

    public void setPosition(int posX, int posY)
    {
        //change position and move all the objects in the list as well
    }
}

Si les variables étaient publiques dans cet exemple, un 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 langages plus récents offrent des propriétés qui nous donnent la syntaxe de Nice des membres de l'éditeur 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 dans Kotlin.

classe VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {field = y; ...}}

Ici, nous avons réalisé la même chose que dans l'exemple Java exemple, mais nous pouvons utiliser posX et posY comme s'il s'agissait de variables publiques. Lorsque j'essaie de changer leur valeur, le corps de l'ensemble de décodage () sera exécuté.

Dans Kotlin par exemple, ce serait l'équivalent d'un Java Bean avec des getters, setters, hashcode, equals et toString implémentés:

data class DataClass(var data: Int)

Remarquez comment cette syntaxe nous permet de faire un Java Bean sur une seule ligne. Vous avez correctement remarqué le problème qu'un langage comme Java a dans l'implémentation de l'encapsulation, mais est la faute de Java pas de l'encapsulation elle-même.

Vous avez dit que vous utilisez @Data de Lombok pour générer des getters et des setters. Notez le nom, @Data. Il est principalement destiné à être utilisé sur des classes de données qui ne stockent que des données et sont destinées à ê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 avec un élément d'interface utilisateur, vous voulez très certainement des setters parce que le simple changement de la valeur d'une variable peut ne pas être suffisant pour obtenir le comportement attendu.

3
BananyaDev

L'encapsulation vous donne flexibilité. En séparant la structure et l'interface, il vous permet de changer la structure sans changer l'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 changer l'interface et apporter des modifications à chaque site d'utilisation.

2
glennsl

Je vais essayer d'illustrer l'espace problématique de l'encapsulation et de la conception des classes, et répondre à votre question à la fin.

Comme mentionné dans d'autres réponses, le but de l'encapsulation est de cacher les détails internes d'un objet derrière une API publique, qui sert de contrat. L'objet est sûr de changer ses internes car il sait qu'il n'est appelé que via l'API publique.

Qu'il soit logique d'avoir des champs publics, des getters/setters, des méthodes de transaction de niveau supérieur ou le passage de messages dépend de la nature du domaine qui est modélisé. Dans le livre Akka Concurrency (que je peux recommander même s'il est quelque peu dépassé), vous trouverez un exemple qui illustre cela, que je vais abréger ici.

Considérons une classe d'utilisateurs:

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

Cela fonctionne très bien dans un contexte monothread. Le domaine modélisé est le nom d'une personne, et les mécanismes de stockage de ce nom peuvent être parfaitement encapsulés par les setters.

Cependant, imaginez que cela doit être fourni dans un contexte multithread. Supposons qu'un thread lit périodiquement le nom:

System.out.println(user.getFirstName() + " " + user.getLastName());

Et deux autres fils mènent une lutte acharnée, la définissant à Hillary Clinton et Donald Trump à leur tour. Ils doivent chacun appeler deux méthodes. Généralement, cela fonctionne bien, mais de temps en temps, vous verrez un Hillary Trump ou un Donald Clinton passer.

Vous ne pouvez pas résoudre ce problème en ajoutant un verrou à l'intérieur des setters, car le verrou n'est maintenu que pendant la durée de la définition du prénom ou du nom de famille. La seule solution par le verrouillage consiste à ajouter un verrou autour de l'objet entier, mais cela rompt l'encapsulation car le code appelant doit gérer le verrou (et peut provoquer des interblocages).

Il s'avère qu'il n'y a pas de solution propre grâce au verrouillage. La solution propre est d'encapsuler à nouveau les composants internes en les rendant plus grossiers:

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

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 pur objet de données 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 setter restant, de sorte que le nom ne peut être modifié que dans son ensemble. Il encapsule plus de son état interne derrière l'API.

Quel est le sens de l'ensemble de ce cycle? Et l'encapsulation a-t-elle vraiment un sens maintenant dans la programmation réelle?

Ce que vous voyez dans ce cycle, ce sont des tentatives d'appliquer trop largement des solutions adaptées à un ensemble spécifique de circonstances. Un niveau d'encapsulation approprié 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 d'encapsulation lui-même, c'est-à-dire la dissimulation des éléments internes derrière une API stable, est la clé de la programmation de logiciels à grande échelle, en particulier dans les systèmes multithreads.

2
Joeri Sebrechts

Je peux penser à un cas d'utilisation où cela a du sens. Vous pourriez avoir une classe à laquelle vous accédez à l'origine via une simple API getter/setter. Vous étendez ou modifiez ultérieurement afin qu'il n'utilise plus les mêmes champs, mais prend toujours en charge la même API.

Un exemple quelque peu artificiel: un point qui commence comme une paire cartésienne avec p.x() et p.y(). Plus tard, vous créez une nouvelle implémentation ou sous-classe qui utilise des coordonnées polaires, vous pouvez donc également appeler p.r() et p.theta(), mais votre code client qui appelle p.x() et p.y() reste valide. La classe elle-même convertit de manière transparente à partir de la forme polaire interne, c'est-à-dire que y() serait désormais return r * sin(theta);. (Dans cet exemple, définir uniquement x() ou y() n'a pas autant de sens, mais est toujours possible.)

Dans ce cas, vous pourriez vous retrouver à dire: "Heureux d'avoir pris la peine de déclarer automatiquement les getters et les setters au lieu de rendre les champs publics, ou j'aurais dû casser mon API là-bas."

1
Davislor

Je ne suis pas réellement un développeur Java; mais ce qui suit est à peu près indépendant de la plate-forme.

Presque tout ce que nous écrivons utilise des getters et setters publics qui accèdent à des variables privées. La plupart des getters et setters sont triviaux. Mais lorsque nous décidons que le setter a besoin de recalculer quelque chose ou que le setter fait une validation ou que nous devons transmettre la propriété à une propriété d'une variable membre de cette classe, cela est complètement insécable pour tout le code et est compatible binaire, donc nous peut échanger ce module.

Lorsque nous décidons que cette propriété doit vraiment être calculée à la volée, tout le code qui la regarde ne doit pas changer et seul le code qui y écrit doit changer et le IDE peut trouver Lorsque nous décidons que c'est un champ calculé accessible en écriture (nous n'avons dû le faire que quelques fois), nous pouvons le faire aussi. La bonne chose est que plusieurs de ces changements sont compatibles binaires (passer en lecture seule le champ calculé n'est pas en théorie mais pourrait être en pratique de toute façon).

Nous nous sommes retrouvés avec beaucoup, beaucoup de getters triviaux avec des setters compliqués. Nous nous sommes également retrouvés avec quelques getters de mise en cache. Le résultat final est que vous êtes autorisé à supposer que les getters sont raisonnablement bon marché mais que les setters ne le sont pas. D'un autre côté, nous sommes assez sages pour décider que les setters ne persistent pas sur le disque.

Mais j'ai dû retrouver le gars qui changeait aveuglément toutes les variables membres en propriétés. Il ne savait pas ce qu'était atomic add alors il a changé la chose qui devait vraiment être une variable publique en une propriété et a cassé le code d'une manière subtile.

0
Joshua

Quelqu'un pourrait-il m'expliquer, quel est le sens de cacher tous les champs comme privés et ensuite de les exposer tous par une technologie supplémentaire?

Cela ne sert à rien. Cependant, le fait que vous posiez cette question démontre que vous n'avez pas compris ce que fait Lombok et que vous ne comprenez pas comment écrire OO code avec encapsulation. Revenons un peu en arrière ...

Certaines données pour une instance de classe seront toujours internes et ne devraient jamais être exposées. Certaines données d'une instance de classe devront être définies en externe et certaines données devront peut-être être retransmises hors d'une instance de classe. Nous pouvons vouloir changer la façon dont la classe fait les choses sous la surface, donc nous utilisons des fonctions pour nous permettre d'obtenir et de définir des données.

Certains programmes veulent enregistrer l'état des instances de classe, elles peuvent donc avoir une interface de sérialisation. Nous ajoutons plus de 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 classe contrôle toujours ses propres données. Nous pouvons sérialiser des données privées, mais le reste du programme n'y a pas accès (ou plus précisément, nous maintenons un Mur chinois en choisissant de ne pas corrompre délibérément ces données privées), et le l'instance de classe peut (et devrait) effectuer des vérifications 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é ou des trucs comme ça. 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.

Cependant, vous constatez fréquemment qu'un paramètre défini en externe est stocké dans une seule variable. Dans ce cas, vous auriez 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-même à chaque fois vous ralentit et est sujette à des erreurs. L'automatisation du processus avec Lombok accélère votre développement et élimine la possibilité d'erreurs.

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 pourrions vouloir changer la façon dont la classe fait les choses sous la surface ..." Si vous rendez votre variable publique, vous contraignez votre code maintenant et pour toujours à avoir cette variable publique comme 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 à l'avenir.

Est-ce que cela le rend plus clair?

0
Graham