Utiliser les finalistes publics plutôt que privés

51

Je vois les POJO les plus immuables écrits comme ceci:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

Pourtant, j'ai tendance à les écrire comme ceci:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

Notez que les références sont finales, donc l'objet est toujours immuable. Cela me permet d’écrire moins de code et autorise un accès plus court (par 5 caractères: le getet ()).

Le seul inconvénient que je peux voir, c'est que si vous voulez modifier la mise en œuvre de getFoo()la suite pour faire quelque chose de fou, vous ne pouvez pas. Mais de manière réaliste, cela ne se produit jamais car l'objet est immuable; vous pouvez vérifier pendant l'instanciation, créer des copies défensives immuables pendant l'instanciation (voir Guava, ImmutableListpar exemple) et préparer les objets fooou barpour l' getappel.

Y a-t-il des inconvénients qui me manquent?

MODIFIER

Je suppose qu’un autre inconvénient qui me manque est la sérialisation des bibliothèques utilisant la réflexion par rapport aux méthodes commençant par getou is, mais c’est une pratique plutôt terrible ...

Cory Kendall
la source
Que se passe-t-il? finalne fait pas une variable l'objet immuable. J'utilise normalement une conception dans laquelle je définis des finalchamps avant de créer un rappel afin que ce dernier puisse accéder à ces champs. Bien sûr, il peut appeler toutes les méthodes, y compris toutes les setXméthodes.
Tomáš Zato
@ TomášZato Il n'y a pas de méthodes setX sur String, intou MyObject. Dans la première version, il finals'agit uniquement de s'assurer que des méthodes autres que le constructeur de la classe n'essayent pas bar = 7;, par exemple. Dans la deuxième version, finalest nécessaire pour empêcher les consommateurs de faire: MyObject x = new MyObject("hi", 5); x.bar = 7;.
Cory Kendall
1
Eh bien votre « Notez les références sont finales, donc le Objectreste immuable. » Est trompeur - de cette façon , il semble que vous pensez que tout final Objectest immuable, qu'il n'est pas. Désolé pour le malentendu.
Tomáš Zato
3
Si les valeurs sous-jacentes étaient modifiables, la route privée + accesseurs ne l’empêcherait pas non plus - myObj.getFoo().setFrob(...).
Beni Cherniavsky-Paskin

Réponses:

38

Quatre inconvénients auxquels je peux penser:

  1. Si vous voulez avoir une forme en lecture seule et modifiable de la même entité, un modèle commun consiste à avoir une classe immuable Entity qui expose uniquement les accesseurs avec des variables de membre protégées, puis créez une MutableEntity qui l'étend et ajoute des setters. Votre version l’empêche.
  2. L'utilisation de getters et de setters est conforme à la convention JavaBeans. Si vous souhaitez utiliser votre classe en tant que bean dans des technologies basées sur les propriétés, telles que JSTL ou EL, vous devez exposer les getters publics.
  3. Si vous souhaitez jamais modifier l'implémentation pour dériver les valeurs ou les rechercher dans la base de données, vous devez refactoriser le code client. Une approche accesseur / mutateur ne vous permet de modifier que l'implémentation.
  4. Le moindre étonnement - quand je vois des variables d'instance publiques, je cherche immédiatement qui peut le muter et crains d'ouvrir la boîte de Pandore car l'encapsulation est perdue. http://en.wikipedia.org/wiki/Principle_of_least_astonishment

Cela dit, votre version est nettement plus concise. S'il s'agit d'une classe spécialisée utilisée uniquement dans un package spécifique (la portée du package est peut-être une bonne idée ici), je peux envisager cette possibilité pour des éléments uniques. Mais je n'exposerais pas les principales API comme celle-ci.

Brandon
la source
3
Grandes réponses; Je suis en désaccord avec certains d’entre eux, mais je ne suis pas sûr que ce site soit le meilleur forum de discussion. En bref, 1) je peux casser les autres en utilisant la forme mutable où ils supposent que c'est immuable (peut-être que je devrais ajouter final à la classe dans mes exemples), 2) Je suis d'accord, 3) Je dirais que ce n'est plus un POJO dans ce cas. cas, 4) Je suis d’accord pour le moment, mais j'espère que de telles discussions peuvent changer ce qui est le moins étonnant :).
Cory Kendall
2
5) Vous ne pouvez pas exposer en toute sécurité des classes / types intrinsèquement mutables - comme des tableaux. Le moyen le plus sûr est de renvoyer une copie d'un getter à chaque fois.
Clockwork-Muse
@ Clockwork-Muse vous pouvez, si vous supposez que les gens ne sont pas des idiots. Lors de l'accès à someObj.thisList.add (), il est évident que vous modifiez l'état de certains autres objets. Avec someObj.getThisList (). Add (), c'est inconnu. Vous devez demander la documentation ou rechercher le source, mais il est impossible de dire à partir de la déclaration de la méthode seulement.
Kritzikratzi
2
@kritzikratzi - Le problème est que vous ne pouvez pas garantir que votre code ne rencontrera pas d'idiots. À un moment donné, ce sera le cas (qui pourrait même finir par être soi-même!), Et la surprise pourrait être un énorme problème.
Clockwork-Muse
1
Bien, laisser tout type mutable hériter d'un type immuable est fondamentalement faux. Rappelez-vous qu'un type immuable donne la garantie "Je ne change pas. Jamais.".
Déduplicateur
26

Débarrassez-vous des getters / setters aussi, et tout va bien!

C'est un sujet très controversé parmi les programmeurs Java.

Quoi qu'il en soit, il y a deux situations où j'utilise des variables publiques à la place (!) Des getters / setters:

  1. public final Pour moi, cela signifie "Je suis immuable", bien mieux qu’un simple getter. La plupart des IDE indiqueront le modificateur final avec un 'F' lors de la complétion automatique. Contrairement aux getters / setters, où vous devez rechercher l'absence d'un setXXX.
  2. public non final J'adore ça pour les classes de données. Je viens d'exposer tous les champs publiquement. Pas de getters, setters, constructeurs. Non rien. Moins qu'un pojo. Pour moi, cela signifie immédiatement "regarde, je suis idiot. Je tiens des données, c'est tout. C'est VOTRE travail de mettre les bonnes données à l'intérieur de moi". Gson / JAXB / etc. gérer ces classes très bien. Ils sont un bonheur d'écrire. Il n'y a aucun doute sur leur but ou leurs capacités. Et plus important encore: vous savez qu'il n'y a pas d'effets secondaires lorsque vous modifiez une variable. IMHO cela aboutit à des modèles de données très concis avec peu d'ambiguïtés, alors que les accesseurs et les setters ont cet énorme problème où la magie se produit parfois à l'intérieur de ceux-ci.
kritzikratzi
la source
5
C'est bien de voir de temps en temps un peu de santé mentale :)
Navin
2
Jusqu'au jour où votre modèle évolue et où l'un des anciens champs peut maintenant être dérivé d'un autre. Puisque vous souhaitez conserver la compatibilité ascendante, l'ancien champ doit rester, mais au lieu de changer simplement le code de lecture et de définition, vous devez le modifier partout où cet ancien champ a été utilisé afin de garantir qu'il respecte la nouvelle représentation du modèle. Facile à écrire… ouais, bien sûr… cauchemar à maintenir à long terme… C'est pourquoi nous avons getter / setter ou, mieux encore, dans les accesseurs de propriétés C #.
Newtopian
9

En termes simples:

  • Vous violez l’encapsulation afin de sauvegarder quelques lignes de code. Cela va à l'encontre du but de OOD.
  • Le code client sera durement couplé aux noms des membres de votre classe. Le couplage est mauvais. L’objectif même de l’OOD est d’empêcher le couplage.
  • Vous êtes également très sûr que votre classe n'aura jamais besoin d'être mutable. Les choses changent. Le changement est la seule chose qui soit constante.
Tulains Córdova
la source
6
En quoi est-ce une violation de l'encapsulation? Les deux versions sont aussi exposées l'une à l'autre au monde extérieur (avec un accès en lecture seule). Vous avez raison au sujet du couplage difficile et de la rigidité du changement, cependant, je ne le discuterai pas.
KChaloux
4
@KChaloux Vous confondez "violation d'encapsulation" avec "code ne compilant pas et devant perdre du temps à le réparer", on arrive en premier. La seconde arrive plus tard. Pour être clair: l'encapsulation est déjà violée dans le présent. Les entrailles de votre classe ne sont plus encapsulées dans le temps présent. Mais le code client sera rompu à l'avenir si les classes changent à l'avenir, car l'encapsulation a été rompue dans le passé. Il y a deux "pauses" différentes, l'encapsulation aujourd'hui, le code client demain, à cause du manque d'encapsulation.
Tulains Córdova
8
Techniquement, lorsque vous utilisez les getters / etc. le code client sera plutôt associé aux noms de méthodes. Regardez combien de bibliothèques doivent inclure les anciennes versions de méthodes avec une balise / annotation "obsolète", car l'ancien code dépend toujours d'eux (et certaines personnes peuvent toujours les utiliser car elles trouvent qu'il est plus facile de les utiliser, mais vous ne l'êtes pas. censé le faire).
JAB
5
Pragmatique: combien de fois les programmeurs refacturent-ils les noms de champs privés sans renommer les getters / setters publics? D'après ce que je peux dire, une convention à l'échelle de la communauté a été mise en place pour les nommer de la même manière, même si cette convention ne peut résulter que d'outils IDE générant des getters et des setters à partir de noms de champs. Théorique: Du point de vue de la modélisation orientée objet, cependant, tout élément public fait partie du contrat public et non un détail de mise en œuvre interne. Donc , si quelqu'un décide de rendre public final sur le terrain, ne sont pas ils disent que le c'est le contrat qu'ils promettent de remplir?
Thomas Jung
3
@ user949300 Dites-moi lesquels pour que je puisse apprendre.
Tulains Córdova
6

Un inconvénient possible que je peux constater immédiatement est que vous êtes lié à la représentation interne des données dans la classe. Ce n'est probablement pas une grosse affaire, mais si vous utilisez les setters et que vous décidez que foo et bar seront retournés par une autre classe définie ailleurs, les classes consommant MyObject ne seront pas obligées de changer. Il vous suffira de toucher MyObject. Cependant, si vous utilisez les valeurs nues, vous devrez toucher partout où votre MyObject est utilisé.

ipaul
la source