L’encapsulation est-elle toujours l’un des objectifs de la POO?

97

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?

Gangnus
la source
19
"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?
David
78
L'encapsulation masque les éléments internes de l'implémentation derrière l'objet de son contrat public (généralement une interface). Les accesseurs et les setters font exactement le contraire: ils exposent les éléments internes de l'objet. Le problème réside donc dans les accesseurs / setters, et non dans l'encapsulation.
22
Les classes de données @VinceEmigh n'ont aucune encapsulation . Des exemples de celles-ci sont des valeurs au sens exact des primitives.
Caleth
10
@VinceEmigh en utilisant JavaBeans n'est pas OO , c'est procédural . Que la littérature les appelle "objets" est une erreur de l'histoire.
Caleth
28
J'ai beaucoup réfléchi à cette question au fil des ans; Je pense que c'est un cas où l'intention de la programmation orientée objet diffère de la mise en œuvre. Après avoir étudié SmallTalk, il était clair que l’encapsulation de la programmation orientée objet était destinée à chaque classe (c’est-à-dire que chaque classe ressemblait à un ordinateur indépendant, avec des méthodes comme protocole partagé), bien que, pour des raisons que je ne connaissais pas encore, sa popularité ait définitivement gagné en popularité. Les objets getter / setter qui n'offrent aucune encapsulation conceptuelle (ils ne cachent rien, ne gèrent rien et n'ont aucune responsabilité autre que les données), et pourtant ils utilisent toujours des propriétés.
HJR

Réponses:

82

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:

  • Peu importe le nombre d'attributs que vous exposez par le biais de getters / setters et la prudence avec laquelle vous le faites. Etre plus sélectif ne fera pas de votre code OOP avec encapsulation. Chaque attribut que vous exposez conduira à une procédure utilisant ces données nues de manière procédurale impérative. Vous allez simplement répandre votre code moins lentement en étant plus sélectif. Cela ne change pas le noyau.
  • Oui, dans les limites du système, vous obtenez des données nues provenant 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 fiables . L'idée même des objets est d'être responsable afin que vous n'ayez pas besoin de donner des ordres directs et impératifs. Au lieu de cela, vous demandez à un objet de faire ce qu'il fait bien dans le contrat. Vous déléguez en toute sécurité une partie agissante à l' objet. Les objets encapsulent les détails d'état et d'implémentation.

Donc, si nous revenons à la question, pourquoi devrions-nous faire cela? Considérons 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 un document exposant les détails internes par getter et un code procédural externeprintDocument 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.

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

Izbassar Tolegen
la source
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft
1
"Getters / setters aussi mauvais que des champs publics" - ce n'est pas vrai, du moins dans de nombreuses langues. En règle générale, vous ne pouvez pas ignorer l'accès à un champ brut, mais vous pouvez également remplacer les getters / setters. Cela donne de la polyvalence aux cours pour enfants. Cela facilite également le changement de comportement des classes. Par exemple, vous pouvez commencer avec un getter / setter sauvegardé par un champ privé et ensuite passer à un autre champ ou calculer la valeur à partir d'autres valeurs. Ni peut être fait avec des champs simples. Certaines langues permettent aux champs d’avoir ce qui est essentiellement automatique des getters et des setters, mais Java n’est pas un tel langage.
Kat
Eh, toujours pas convaincu. Votre exemple est correct, mais il ne s'agit que d'un style de codage différent, pas du "vrai bon" - qui n'existe pas. Gardez à l'esprit que la plupart des langues sont aujourd'hui multiparadigiques et sont rarement purement procédurales. Tenez-vous si fort aux "concepts purement OO". Par exemple, vous ne pouvez pas utiliser DI dans votre exemple car vous avez associé la logique d'impression à une sous-classe. L'impression ne devrait pas faire partie d'un document - un document imprimable exposerait sa partie imprimable (via un outil de lecture) à un PrinterService.
T. Sar - Rétablir Monica
Une approche OO appropriée à cela, si nous passions à une approche "Pure OO", utiliserait quelque chose qui implémente une classe abstraite PrinterService demandant, via un message, ce que l'enfer doit être imprimé - en utilisant une sorte GetPrintablePart. Ce qui implémente PrinterService peut être n’importe quel type d’imprimante: impression sur un fichier PDF, à l’écran, sur le fichier TXT ... Votre solution rend impossible l’échange de la logique d’impression pour une autre opération, pour un résultat plus couplé et moins gérable. que votre "mauvais" exemple.
T. Sar - Rétablir Monica
La ligne du bas: Getters et Setters ne sont pas diaboliques ou ne brisent pas la POO - Les gens qui ne comprennent pas comment les utiliser le sont. Votre exemple, cas est un exemple classique de personnes qui ne comprennent pas tout le fonctionnement de DI. L'exemple que vous "corrigez" était déjà activé pour DI, découplé, pouvait être facilement moqué ... Vraiment - vous vous souvenez de l'ensemble "Préférer la composition à l'héritage"? Un des piliers OO? Vous venez de le jeter par la fenêtre en modifiant le code. Cela ne serait pas possible dans les revues de code sérieuses.
T. Sar - Rétablir Monica
76

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 ne pas simplement utiliser que des champs publics alors?

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.

Michael Borgwardt
la source
20
"Les joueurs et les setters ne constituent pas nécessairement un simple accès" - je pense que c'est un point clé. Si votre code accède à un champ par son nom, vous ne pouvez pas modifier le comportement ultérieurement. Si vous utilisez obj.get (), vous pouvez.
Dan Ambrogio
4
@ jrh: Je n'ai jamais entendu parler d'une mauvaise pratique en Java, et c'est assez courant.
Michael Borgwardt
2
@MichaelBorgwardt Intéressant; un peu hors sujet, mais je me suis toujours demandé pourquoi Microsoft recommandait de ne pas le faire pour C #. Je suppose que ces directives impliquent que dans C #, les réglages ne peuvent être utilisés que par la conversion d'une valeur donnée en une autre valeur utilisée en interne, d'une manière qui ne peut pas échouer ou avoir une valeur non valide (par exemple, avoir une propriété PositionInches et une propriété PositionCentimeters où il se convertit automatiquement en mm)? Pas un bon exemple, mais c'est le meilleur que je puisse trouver pour le moment.
HJR
2
@jrh cette source dit de ne pas le faire pour les accesseurs, par opposition aux setters.
Captain Man
3
@Gangnus et vous voyez vraiment que quelqu'un cache une certaine logique à l'intérieur de getter / setter? Nous sommes tellement habitués à cela que cela 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.
Izbassar Tolegen
17

Je pense que le noeud de la question est expliqué par votre commentaire:

Je suis totalement d'accord avec ta pensée. Mais quelque part, nous devons charger des objets avec des données. Par exemple, à partir de XML. Et les plates-formes actuelles le supportant le font par le biais de getters / setters, dégradant ainsi la qualité de notre code et notre façon de penser. Lombok, en réalité, n’est 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 modèle de données actif .

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,
  • ...

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:

  • Independent : pour que vous puissiez ajouter / supprimer des propriétés sur la nomenclature en fonction de vos besoins sans avoir à modifier tous les clients / serveurs, ...
  • isolée : de sorte 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 d'ondulation dans la base de code.

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

Matthieu M.
la source
3
Je ne créerais pas de getters et de setters pour des objets de communication. Je voudrais juste rendre les champs publics. Pourquoi créer plus de travail qu'il n'est utile?
user253751
3
@immibis: Je suis d'accord en général, cependant, comme l'a noté le PO, certains cadres nécessitent des accesseurs / régleurs, auquel cas vous devez simplement vous conformer. Notez que l'OP utilise une bibliothèque qui les crée automatiquement avec l'application d'un seul attribut, ce qui représente donc peu de travail pour lui.
Matthieu M.
1
@Gangnus: L'idée ici est que les getters / setters sont isolés de la couche limite (I / O), où il est normal de se salir, mais le reste de l'application (code pur) n'est pas contaminé et reste élégant.
Matthieu M.
2
@kubanczyk: la définition officielle est le modèle d'objet métier à ma connaissance. Cela correspond ici à ce que j'ai appelé le "modèle de données interne", le modèle de données sur lequel vous exécutez la logique dans le noyau "pur" de votre application.
Matthieu M.
1
C'est l'approche pragmatique. Nous vivons dans un monde hybride. Si les bibliothèques externes pouvaient savoir quelles données transmettre aux constructeurs de nomenclatures, nous n'aurions que des nomenclatures.
Adrian Iftode
12

Considérer ce qui suit..

Vous avez une Userclasse avec une propriété int age:

class User {
    int age;
}

Vous voulez augmenter cela afin d' Useravoir une date de naissance, opposée à seulement un âge. À l'aide d'un getter:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Nous pouvons échanger le int ageterrain pour un plus complexe LocalDate dateOfBirth:

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 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' @Dataannotation 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.

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

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 @Getteret @Setterutilise indépendamment les besoins de vos besoins.

Vince Emigh
la source
2
Cela n'a rien à voir avec le fait de gifler l' @Dataannotation sur un type qui, par
définition
1
Non, les champs ne sont pas cachés . Parce que tout peut arriver etsetAge(xyz)
Caleth
9
@ Caleth Les champs sont cachés. Vous agissez comme si les setters ne pouvaient pas avoir de conditions avant et après, ce qui est un cas d'utilisation très courant pour utiliser des setters. Vous agissez comme si une propriété de champ ==, qui même dans des langages tels que C #, n'est tout simplement pas vraie, car les deux ont tendance à être supportées (comme en C #). Le champ peut être déplacé vers une autre classe, il peut être échangé pour une représentation plus complexe ...
Vince Emigh
1
Et ce que je dis, c'est que ce n'est pas important
Caleth
2
@ Caleth Comment est-ce pas important? Cela n'a aucun sens. Vous dites que l'encapsulation ne s'applique pas parce que vous estimez que ce n'est pas important dans cette situation, même si cela s'applique par définition et a ses cas d'utilisation?
Vince Emigh
11

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

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.

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.

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 @Datafait plus que générer des getters / setters: Il génère aussi toString(), hashCode()et equals(Object)qui peut être très utile.

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

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.

meriton - en grève
la source
1
applique ses invariants? Est-ce l'anglais? Pourriez-vous l'expliquer à un non-Anglais, s'il vous plaît? Ne soyez pas trop compliqué - certaines personnes ici ne connaissent pas suffisamment l'anglais.
Gangnus
J'aime votre base, j'aime vos pensées (+1), mais pas le résultat pratique. Je préférerais utiliser un champ privé et une bibliothèque spéciale pour charger des données par réflexion. Je ne comprends pas pourquoi vous posez votre réponse comme un argument contre moi?
Gangnus
@Gangnus Un invariant est une condition logique qui ne change pas (en sens non et sens -variant varie / change). De nombreuses fonctions ont des règles qui doivent être vraies avant et après leur exécution (appelées pré-conditions et post-conditions), sinon il y a une erreur dans le code. Par exemple, une fonction peut nécessiter que son argument ne soit pas null (une condition préalable) et peut lever une exception si son argument est nul, car ce cas serait une erreur dans le code invariant).
Pharap
@Gangus: Pas vraiment sûr où la réflexion entre dans cette discussion; Lombok est un processeur d'annotation, c'est-à-dire un plug-in du compilateur Java qui émet du code supplémentaire lors de la compilation. Mais bien sûr, vous pouvez utiliser Lombok. Je dis simplement que dans de nombreux cas, les champs publics fonctionnent aussi bien et sont plus faciles à configurer (tous les compilateurs ne détectent pas automatiquement les processeurs d'annotations ...).
meriton - en grève
1
@Gangnus: Les invariants sont les mêmes en math et en CS, mais dans CS, il existe une perspective supplémentaire: Du point de vue d'un objet, les invariants doivent être appliqués et établis. Du point de vue de l'appelant de cet objet, les invariants sont toujours vrais.
meriton - en grève
7

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:

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

Quoi qu’il en soit, le strict minimum de la mise en œuvre réelle bénéficie toujours beaucoup de l’encapsulation.

Daniu
la source
Python a une caractéristique similaire dans laquelle les propriétés peuvent avoir des getters / setters transparents. Je suis sûr qu'il y a encore plus de langues qui ont des fonctionnalités similaires.
JAB
1
"La prévalence de getters / setters IMO est causée par la spécification Java Bean, qui l'exige" Oui, vous avez raison. Et qui a dit que cela devait être nécessaire? La raison, s'il vous plaît?
Gangnus
2
La méthode C # est absolument identique à celle de Lombok. Je ne vois pas de réelle différence. Plus de commodité, mais évidemment de mauvaise conception.
Gangnus
1
@Gangnis La spécification le requiert. Pourquoi? Regardez ma réponse pour un exemple concret de la raison pour laquelle les getters ne sont pas les mêmes que ceux qui exposent des champs - essayez de faire la même chose sans getter.
Vince Emigh
@VinceEmigh gangnUs, s'il vous plaît :-). (marche en groupe, nus - noix). Et que dire de l'utilisation de get / set uniquement là où nous en avons spécifiquement besoin et du chargement en masse dans des champs privés par réflexion dans d'autres cas?
Gangnus
6

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 ne pas simplement utiliser que des champs publics alors? Je pense que nous avons parcouru un chemin long et difficile avant de revenir au point de départ.

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.

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?

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.

entrez la description de l'image ici

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.

JimmyJames
la source
2
Les pensées les plus concrètes et les plus sages. +1 Mais pourquoi ne pas écrire un groupe de classes, dont chacune chargera / sauvegardera massivement des champs privés vers / depuis une structure externe? JSON, XML, autre objet. Cela pourrait fonctionner avec des POJO ou, peut-être, seulement avec des champs, marqués par une annotation pour cela. Maintenant, je pense à cette alternative.
Gangnus
@Gangnus À deviner parce que cela signifie beaucoup d'écriture de code supplémentaire. Si la réflexion est utilisée, un seul bloc de code de sérialisation doit être écrit et il peut sérialiser toute classe qui suit le modèle spécifique. C # supporte cela à travers l' [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?
Pharap
@Pharap Je suis terriblement désolé, mais votre commentaire est trop bref pour moi. "tirer parti de la réflexion"? Et quel est le sentiment de cacher des choses si différentes dans une classe? Pourriez-vous expliquer ce que vous voulez dire?
Gangnus
@Gangnus Je parle de réflexion , capacité du code à inspecter sa propre structure. En Java (et dans d’autres langages), vous pouvez obtenir une liste de toutes les fonctions qu’une classe expose, puis les utiliser pour extraire les données de la classe requise afin de les sérialiser, par exemple, XML / JSON. Utiliser la réflexion comme un travail ponctuel au lieu de créer constamment de nouvelles méthodes pour sauvegarder les structures classe par classe. Voici un exemple simple en Java .
Pharap
@Pharap C'est exactement ce dont je parlais. Mais pourquoi l’appelez-vous «levier» Et l’alternative que j’ai mentionnée dans le premier commentaire reste: les champs (non) compactés doivent-ils avoir des annotations spéciales ou non?
Gangnus
5

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 returnmot 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 returnpartenaire, 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:

Entrez la description de l'image ici

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, alors utiliser des accesseurs est un choix. Pas un mal nécessaire.

confits_orange
la source
Oui, ces getters / setters ont beaucoup de sens. Mais comprenez-vous que cela concerne autre chose? :-) +1 quand même.
Gangnus
1
@ Gangnus Merci. Il y a beaucoup de choses dont vous pourriez parler. Si vous vouliez au moins les nommer, je pourrais les aborder.
candied_orange
5

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 à @Datacela 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à! @DataLes 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.

JacquesB
la source
L'encapsulation devrait nous permettre le polymorphisme et la sécurité. Gets / sets permettent les sapins, mais sont fortement contre le second.
Gangnus
Si vous dites que j'utilise un dogme quelque part, dites-moi quel est le dogme de mon texte. Et n’oubliez pas que certaines pensées que j’utilise ne me plaisent pas ou ne me conviennent pas. Je les utilise pour démontrer une contradiction.
Gangnus
1
"GangNus" :-). Il y a une semaine, j'ai travaillé sur un projet littéralement rempli de getters et de setters sur plusieurs niveaux. C'est absolument dangereux et, pire encore, cela aide les gens à utiliser un code non sécurisé. Je suis content d' avoir changé d' emploi assez vite, pour les gens deviennent habitués à cette manière. Donc, je ne le pense pas, je le vois .
Gangnus
2
@jrh: Je ne sais pas s'il existe une explication formelle en tant que telle, mais C # a un support intégré pour les propriétés (getters / setters avec une syntaxe plus agréable) et pour les types anonymes qui sont des types avec uniquement des propriétés et aucun comportement. Ainsi, les concepteurs de C # se sont délibérément écartés de la métaphore de la messagerie et du principe "Dites, ne demandez pas". Le développement de C # depuis la version 2.0 semble s’être davantage inspiré des langages fonctionnels que de la pureté d’OO traditionnelle.
JacquesB
1
@ jrh: J'imagine maintenant, mais je pense que C # est assez inspiré par les langages fonctionnels où les données et les opérations sont plus séparées. Dans les architectures distribuées, la perspective est également passée de la technologie orientée message (RPC, SOAP) à la technologie orientée données (REST). Et les bases de données qui exposent et exploitent des données (pas des objets "boîte noire") ont prévalu. En bref, je pense que l'attention est passée des messages entre les boîtes noires aux modèles de données exposés
JacquesB
3

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:

class DataClass 
{
    private int data;

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

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.

class DataClass implements IDataInterface
{
    private int data;

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

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:

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

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:

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

Sans encapsulation, cela ressemblerait à ceci:

player.posX++;

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:

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, 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:

data class DataClass(var data: Int)

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.

BananyaDev
la source
Pourriez-vous donner ici un exemple de mauvaise utilisation de l'encapsulation? Cela pourrait être intéressant. Votre exemple est plutôt contre le manque d'encapsulation.
Gangnus
1
@ Gangnus, j'ai ajouté quelques exemples. L'encapsulation inutile est généralement une utilisation abusive et, d'après mon expérience, les développeurs d'API essaient trop de vous forcer à utiliser une API d'une certaine manière. Je n’ai pas donné d’exemple à ce sujet car je n’avais pas de modèle facile à présenter. Si j'en trouve un, je l'ajouterai définitivement. Je pense que la plupart des commentaires contre l’encapsulation sont en fait contre la syntaxe de l’encapsulation plutôt que pour l’encapsulation elle-même.
BananyaDev
Merci pour l'édition. J'ai cependant trouvé une faute de frappe mineure: "publice" au lieu de "public".
jrh
2

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.

Glennsl
la source
Bien que ce soit une bonne idée en théorie, rappelez-vous que le fait de forcer la version 2 d'une API à lever des exceptions que la version 1 n'empêcherait pas les utilisateurs de votre bibliothèque ou de votre classe terriblement (et peut-être silencieusement!); Je suis un peu sceptique sur le fait que tout changement majeur pourrait être effectué "en coulisse" sur la plupart de ces classes de données.
jrh
Désolé, ce n'est pas une explication. Le fait que l'utilisation de champs soit interdite dans les interfaces vient de l'idée d'encapsulation. Et maintenant nous en parlons. Vous vous basez sur les faits discutés. (remarquez, je ne parle pas pour ou contre vos pensées, seulement qu'elles ne peuvent pas être utilisées ici comme arguments)
Gangnus
@Gangnus Le mot "interface" a une signification en dehors du mot-clé Java: dictionary.com/browse/interface
glennsl
@jrh C'est un problème totalement différent. Vous pouvez l'utiliser pour argumenter contre l'encapsulation dans certains contextes , mais cela n'en invalide aucunement les arguments.
Glennsl
1
L'ancienne encapsulation, sans get / set de masse, nous a donné non seulement le polymorphisme, mais également la sécurité. Et la masse établit / obtient nous apprendre à la mauvaise pratique.
Gangnus
2

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:

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 à 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:

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

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:

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

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?

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.

Joeri Sebrechts
la source
Fantastique. Je dirais que vous avez soulevé la discussion à un niveau plus élevé. Peut-être deux. Vraiment, je pensais comprendre l’encapsulation et parfois mieux que des livres classiques, et j’avais vraiment peur que nous le perdions pratiquement à cause de certaines coutumes et technologies acceptées, et ÇA était le véritable sujet de ma question (peut-être, non formulé dans un bonne voie), mais vous m'avez montré de nouveaux côtés de l'encapsulation. Je ne suis certainement pas doué pour le multitâche et vous m'avez montré à quel point il pouvait être dangereux de comprendre d'autres principes de base.
Gangnus 10/10
Mais je ne peux pas marquer votre message comme la réponse, car il ne s'agit pas du chargement en masse de données dans / à partir d'objets, mais du problème en raison duquel des plates-formes telles que beans et Lombok apparaissent. Peut-être pourriez-vous préciser votre pensée dans cette direction, s'il vous plaît, si vous en avez le temps? Moi-même, je n'ai pas encore repensé les conséquences que votre pensée apporte à ce problème. Et je ne suis pas sûr d'être suffisamment en forme pour cela (rappelez-vous, mauvais fond multithreading: - [)
Gangnus 10/10
Je n'ai pas utilisé lombok, mais si j'ai bien compris, il s'agit d'un moyen d'implémenter un niveau spécifique d'encapsulation (getters / setters sur chaque champ) avec moins de dactylographie. Cela ne change pas la forme idéale de l'API pour votre domaine de problème, c'est juste un outil pour l'écrire plus rapidement.
Joeri Sebrechts
1

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()et p.y(). Vous créez ensuite une nouvelle implémentation ou une sous-classe qui utilise les coordonnées polaires. Vous pouvez donc également appeler p.r()et p.theta(), mais votre code client 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 le y()ferait maintenant return r * sin(theta);. (Dans cet exemple, définir seulement x()ou y()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."

Davislor
la source
2
Ce n'est pas si beau que tu le vois. Changer la représentation physique interne rend l'objet vraiment différent. Et il est mauvais qu’ils aient le même aspect, car ils ont des qualités différentes. Le système Descartes n'a pas de points particuliers, le système polaire en a un.
Gangnus
1
Si vous n'aimez pas cet exemple spécifique, vous pouvez sûrement penser aux autres qui ont réellement plusieurs représentations équivalentes, ou une représentation plus récente qui peut être convertie vers et à partir d'une autre.
Davislor
Oui, vous pouvez les utiliser de manière très productive pour une présentation. Dans l'interface utilisateur, il peut être utilisé largement. Mais ne me faites-vous pas répondre à ma propre question? :-)
Gangnus
Plus efficace de cette façon, n'est-ce pas? :)
Davislor
0

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?

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?

Graham
la source
Vous parlez des questions de l'étudiant de première année en informatique. Nous sommes déjà ailleurs. Je ne dirais jamais cela et je vous donnerais même un avantage pour des réponses intelligentes, si vous n’êtes pas si impoli dans la forme de votre réponse.
Gangnus
0

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.

Josué
la source
Bienvenue dans le monde Java. (C # aussi) *soupir*. Mais comme pour utiliser get / set pour masquer la représentation intérieure, soyez prudent. C’est sur le format externe seulement, c’est OK. Mais s’il s’agit de mathématiques, ou, pire, de physique ou autre chose réelle, une représentation interne différente a des qualités différentes. à savoir mon commentaire sur softwareengineering.stackexchange.com/a/358662/44104
Gangnus
@ Gangnus: Cet exemple est extrêmement regrettable. Nous en avons eu plusieurs, mais le calcul a toujours été exact.
Josué
-1

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ù".

HorriblePerson
la source
Dans ces langages "modernes", l'API ressemble à un accès de champ, foo.barmais 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éthode foo.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!).
Warbo
1
1. Cacher la physique n'est pas toujours bon. regardez mon commentaire sur softwareengineering.stackexchange.com/a/358662/44104 . 2. Je ne parle pas de la difficulté ou de la simplicité de la création d'un getter / setter. Le C # a absolument le même problème que nous parlons. Le manque d’incapsulation dû à la mauvaise solution de chargement d’informations en masse. 3. Oui, la solution pourrait être basée sur la syntaxe, mais, s'il vous plaît, d'une manière différente de celle que vous mentionnez. Celles-ci sont mauvaises, nous avons ici une grande page remplie d'explications. 4. La solution ne doit pas nécessairement être basée sur la syntaxe. Cela pourrait être fait dans les limites de Java.
Gangnus
-1

Quelqu'un pourrait-il m'expliquer quel est le sentiment de cacher tous les champs en tant que champs privés et ensuite de les exposer par 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, 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.

radarbob
la source
Oui, les propriétés pour l'interface utilisateur ont un sens particulier, ce sont des représentations publiques et jolies des champs. Bon point.
Gangnus
"Alors pourquoi n'avons-nous pas / utilisions-nous simplement des méthodes réelles?" Techniquement, ils l'ont fait dans la version .Net. Les propriétés sont un sucre syntaxique pour les fonctions get et set.
Pharap
"idiotocratie" ? Tu ne veux pas dire " idiosyncrasie "?
Peter Mortensen
Je voulais dire idiocratie
radarbob