Est-ce une mauvaise pratique de faire revenir un setter "this"?


249

Est-ce une bonne ou une mauvaise idée de faire revenir les setters en java "this"?

public Employee setName(String name){
   this.name = name;
   return this;
}

Ce modèle peut être utile car vous pouvez alors enchaîner des setters comme ceci:

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

au lieu de cela:

Employee e = new Employee();
e.setName("Jack Sparrow");
...and so on...
list.add(e);

... mais cela va en quelque sorte à l'encontre de la convention standard. Je suppose que cela pourrait valoir la peine simplement parce que cela peut faire que le setter fasse autre chose d'utile. J'ai vu ce modèle utilisé à certains endroits (par exemple JMock, JPA), mais il semble rare, et n'est généralement utilisé que pour des API très bien définies où ce modèle est utilisé partout.

Mettre à jour:

Ce que j'ai décrit est évidemment valable, mais ce que je recherche vraiment, ce sont des réflexions sur la question de savoir si cela est généralement acceptable et s'il y a des écueils ou des meilleures pratiques connexes. Je connais le modèle Builder mais il est un peu plus impliqué que ce que je décris - comme Josh Bloch le décrit, il existe une classe Builder statique associée pour la création d'objets.


1
Depuis que j'ai vu ce modèle de conception il y a quelque temps, je le fais dans la mesure du possible. Si une méthode n'a pas explicitement besoin de renvoyer quelque chose pour faire son travail, elle retourne maintenant this. Parfois, je modifie même la fonction de sorte qu'au lieu de renvoyer une valeur, elle opère sur un membre de l'objet, juste pour que je puisse le faire. C'est merveilleux. :)
Inversus

4
Pour les setters télescopiques qui retournent eux-mêmes et dans les builders, je préfère utiliser withName (String name) au lieu de setName (String name). Comme vous l'avez souligné, une pratique courante et une attente pour le passeur est de retourner nul. Les
décodeurs

Veuillez introduire des sauts de ligne avant chaque appel :) Et configurez votre IDE ou obtenez-en un approprié s'il ne respecte pas cela.
MauganRa

Les frameworks largement utilisés (Spring et Hibernate par exemple) adhéreront strictement (du moins ils le faisaient auparavant) à la convention void-setters
Legna

Réponses:


83

Je ne pense pas qu'il y ait quelque chose de mal en particulier, c'est juste une question de style. C'est utile lorsque:

  • Vous devez définir plusieurs champs à la fois (y compris lors de la construction)
  • vous savez quels champs vous devez définir au moment où vous écrivez le code, et
  • il existe de nombreuses combinaisons différentes pour les champs que vous souhaitez définir.

Des alternatives à cette méthode pourraient être:

  1. Un méga constructeur (inconvénient: vous pourriez passer beaucoup de valeurs nulles ou par défaut, et il devient difficile de savoir quelle valeur correspond à quoi)
  2. Plusieurs constructeurs surchargés (inconvénient: devient encombrant une fois que vous en avez plusieurs)
  3. Méthodes d'usine / statiques (inconvénient: identique aux constructeurs surchargés - devient difficile à manier lorsqu'il y en a plusieurs)

Si vous ne définissez que quelques propriétés à la fois, je dirais que cela ne vaut pas la peine de retourner «ceci». Cela tombe certainement si vous décidez plus tard de renvoyer autre chose, comme un indicateur de statut / succès / message.


1
eh bien, en général, vous ne retournez rien d'un setter, par convention.
Ken Liu

17
Peut-être pas pour commencer, mais un setter ne conserve pas nécessairement son objectif d'origine. Ce qui était autrefois une variable peut se transformer en un état qui englobe plusieurs variables ou avoir d'autres effets secondaires. Certains setters peuvent retourner une valeur précédente, d'autres peuvent retourner un indicateur d'échec si l'échec est trop courant pour une exception. Cela soulève cependant un autre point intéressant: et si un outil / framework que vous utilisez ne reconnaît pas vos setters lorsqu'ils ont des valeurs de retour?
Tom Clift

12
@Tom bon point, cela rompt la convention "Java bean" pour les getters et les setters.
Andy White

2
@TomClift La rupture de la convention "Java Bean" cause-t-elle des problèmes? Sont des bibliothèques qui utilisent la convention "Java Bean" en regardant le type de retour ou seulement les paramètres de la méthode et le nom de la méthode.
Theo Briscoe

3
c'est pourquoi le modèle Builder existe, les setters ne devraient pas retourner quelque chose, à la place, créez un builder si son besoin semble meilleur et prend moins de code :)
RicardoE

106

Ce n'est pas une mauvaise pratique. C'est une pratique de plus en plus courante. La plupart des langages ne vous obligent pas à traiter l'objet retourné si vous ne le souhaitez pas, il ne modifie donc pas la syntaxe d'utilisation du setter "normal" mais vous permet de chaîner les setters ensemble.

Ceci est communément appelé un modèle de générateur ou une interface fluide .

Il est également courant dans l'API Java:

String s = new StringBuilder().append("testing ").append(1)
  .append(" 2 ").append(3).toString();

26
Il est souvent utilisé dans les constructeurs, mais je ne dirais pas "c'est ... appelé un modèle Builder".
Laurence Gonsalves

10
C'est drôle pour moi qu'une partie de la raison d'être des interfaces fluides soit qu'elles rendent le code plus facile à lire. Je pouvais voir qu'il était plus pratique d'écrire, mais cela me semble un peu plus difficile à lire. C'est le seul vrai désaccord que j'ai avec cela.
Brent écrit le code

30
Il est également connu sous le nom d'antipattern épave de train. Le problème est que lorsqu'une trace de pile d'exceptions pointeur nul contient une ligne comme celle-ci, vous ne savez pas quelle invocation a renvoyé la valeur null. Cela ne veut pas dire que le chaînage doit être évité à tout prix, mais méfiez-vous des mauvaises bibliothèques (en particulier celles fabriquées à la maison).
ddimitrov

18
@ddimitrov aussi longtemps que vous le limitez à le renvoyer, ce ne sera jamais un problème (seule la première invocation pourrait lancer NPE)
Stefan

4
il est plus facile d'écrire ET de lire en supposant que vous mettez des sauts de ligne et des indentations où la lisibilité en souffrirait autrement! (car cela évite l'encombrement redondant du code répétitif comme bla.foo.setBar1(...) ; bla.foo.setBar2(...)lorsque vous pouviez écrire bla.foo /* newline indented */.setBar1(...) /* underneath previous setter */ .setBar2(...)(ne peut pas utiliser les sauts de ligne dans un commentaire SO comme celui-ci :-( ... j'espère que vous obtenez le point en considérant 10 de ces setters ou des appels plus complexes)
Andreas Dietrich

90

Résumer:

  • cela s'appelle une "interface fluide", ou "chaînage de méthode".
  • ce n'est pas du Java "standard", bien que vous le voyez de plus en plus ces jours-ci (fonctionne très bien dans jQuery)
  • il viole la spécification JavaBean, il rompra donc avec divers outils et bibliothèques, en particulier les constructeurs JSP et Spring.
  • cela peut empêcher certaines optimisations que la JVM ferait normalement
  • certaines personnes pensent qu'il nettoie le code, d'autres pensent que c'est "horrible"

Quelques autres points non mentionnés:

  • Cela viole le principe selon lequel chaque fonction doit faire une (et une seule) chose. Vous pouvez ou non y croire, mais en Java, je pense que cela fonctionne bien.

  • Les IDE ne vont pas les générer pour vous (par défaut).

  • J'ai enfin, voici un point de données du monde réel. J'ai eu des problèmes avec une bibliothèque construite comme ça. Le générateur de requêtes d'Hibernate en est un exemple dans une bibliothèque existante. Étant donné que les méthodes set * de Query renvoient des requêtes, il est impossible de dire simplement en regardant la signature comment l'utiliser. Par exemple:

    Query setWhatever(String what);
  • Il introduit une ambiguïté: la méthode modifie-t-elle l'objet actuel (votre modèle) ou, peut-être que Query est vraiment immuable (un modèle très populaire et précieux), et la méthode en renvoie un nouveau. Cela rend la bibliothèque plus difficile à utiliser et de nombreux programmeurs n'exploitent pas cette fonctionnalité. Si les colons étaient des colons, il serait plus clair de les utiliser.


5
btw, c'est "fluide", pas "fluide" ... car il vous permet de structurer une série d'appels de méthode comme une langue parlée.
Ken Liu

1
Le dernier point sur l'immuabilité est très important. L'exemple le plus simple est String. Les développeurs Java s'attendent à ce que lorsqu'ils utilisent des méthodes sur String, ils obtiennent une instance totalement nouvelle et pas la même mais modifiée. Avec une interface fluide, il faudrait mentionner dans la documentation de la méthode que l'objet renvoyé est «ceci» au lieu d'une nouvelle instance.
MeTTeO

7
Bien que je sois d'accord dans l'ensemble, je ne suis pas d'accord pour dire que cela viole le principe "ne faire qu'une seule chose". Le retour thisn'est guère compliqué. :-)
user949300

point supplémentaire: il viole également le principe de séparation commande-requête.
Marton_hun

84

Je préfère utiliser les méthodes «avec» pour cela:

public String getFoo() { return foo; }
public void setFoo(String foo) { this.foo = foo; }
public Employee withFoo(String foo) {
  setFoo(foo);
  return this;
}

Donc:

list.add(new Employee().withName("Jack Sparrow")
                       .withId(1)
                       .withFoo("bacon!"));

Attention : cette withXsyntaxe est couramment utilisée pour fournir des "setters" pour les objets immuables, donc les appelants de ces méthodes peuvent raisonnablement s'attendre à ce qu'ils créent de nouveaux objets plutôt que de muter l'instance existante. Peut-être qu'une formulation plus raisonnable serait quelque chose comme:

list.add(new Employee().chainsetName("Jack Sparrow")
                       .chainsetId(1)
                       .chainsetFoo("bacon!"));

Avec la convention de nommage chainsetXyz (), tout le monde devrait être content.


18
+1 Pour une convention intéressante. Je ne vais pas l'adopter dans mon propre code car il semble que maintenant vous devez avoir un get, un setet un withpour chaque champ de classe. C'est toujours une solution intéressante. :)
Paul Manta

1
Cela dépend de la fréquence à laquelle vous appelez les setters. J'ai trouvé que si ces setters sont souvent appelés, cela vaut la peine de les ajouter car cela simplifie le code partout ailleurs. YMMV
qualidafial

2
Et si vous avez ajouté à Project Lombokdes » @Getter/@Setterannotations ... ce serait fantastique pour chaîner. Ou vous pouvez utiliser quelque chose qui ressemble beaucoup au Kestrelcombinateur ( github.com/raganwald/Katy ) utilisé par les démons JQuery et Javascript.
Ehtesh Choudhury

4
@ AlikElzin-kilaka En fait, je viens de remarquer que les classes immuables java.time dans Java 8 utilisent ce modèle, par exemple LocalDate.withMonth, withYear, etc.
qualidafial

4
le withpréfixe est une convention différente. comme @qualidafial a donné un exemple. les méthodes préfixées withne doivent pas renvoyer thismais plutôt une nouvelle instance comme l'instance actuelle mais withqui changent. Cela se fait lorsque vous souhaitez que vos objets soient immuables. Donc, quand je vois une méthode préfixée, withje suppose que j'obtiendrai un nouvel objet, pas le même objet.
tempcke

26

Si vous ne voulez pas revenir 'this'du setter mais ne voulez pas utiliser la deuxième option, vous pouvez utiliser la syntaxe suivante pour définir les propriétés:

list.add(new Employee()
{{
    setName("Jack Sparrow");
    setId(1);
    setFoo("bacon!");
}});

En passant, je pense que c'est légèrement plus propre en C #:

list.Add(new Employee() {
    Name = "Jack Sparrow",
    Id = 1,
    Foo = "bacon!"
});

16
l'initialisation de double accolade peut avoir des problèmes avec des égaux car elle crée une classe interne anonyme; voir c2.com/cgi/wiki?DoubleBraceInitialization
Csaba_H

@Csaba_H De toute évidence, ce problème est la faute de la personne qui a raté la equalsméthode. Il existe des moyens très simples de gérer les classes anonymes equalssi vous savez ce que vous faites.
AJMansfield

créer une nouvelle classe (anonyme) juste pour ça? pour chaque cas?
user85421

11

Il rompt non seulement la convention des getters / setters, il rompt également le cadre de référence de la méthode Java 8. MyClass::setMyValueest un BiConsumer<MyClass,MyValue>, et myInstance::setMyValueest un Consumer<MyValue>. Si vous avez votre retour de setter this, alors ce n'est plus une instance valide de Consumer<MyValue>, mais plutôt un Function<MyValue,MyClass>, et provoquera la rupture de tout ce qui utilisant des références de méthode à ces setters (en supposant qu'il s'agit de méthodes void).


2
Ce serait génial si Java avait un moyen de surcharger par type de retour, pas seulement la JVM. Vous pouvez facilement contourner bon nombre de ces changements de rupture.
Adowrath

1
Vous pouvez toujours définir une interface fonctionnelle qui étend à la fois Consumer<A>et Function<A,B>en fournissant une implémentation par défaut de void accept(A a) { apply(a); }. Ensuite, il peut facilement être utilisé comme l'un ou l'autre et ne cassera aucun code qui nécessite un formulaire spécifique.
Steve K

Argh! Ceci est tout simplement faux! C'est ce qu'on appelle la compatibilité nulle. Un setter qui retourne une valeur peut agir en tant que consommateur. ideone.com/ZIDy2M
Michael

8

Je ne connais pas Java mais je l'ai fait en C ++. D'autres personnes ont dit que cela rend les lignes très longues et très difficiles à lire, mais je l'ai fait comme ça plusieurs fois:

list.add(new Employee()
    .setName("Jack Sparrow")
    .setId(1)
    .setFoo("bacon!"));

C'est encore mieux:

list.add(
    new Employee("Jack Sparrow")
    .Id(1)
    .foo("bacon!"));

du moins, je pense. Mais vous êtes les bienvenus pour me downvote et appelez-moi un horrible programmeur si vous le souhaitez. Et je ne sais pas si vous êtes autorisé à le faire même en Java.


Le «encore mieux» ne se prête pas bien à la fonctionnalité de formatage du code source disponible dans les IDE modernes. Malheureusement.
Thorbjørn Ravn Andersen

vous avez probablement raison ... Le seul formateur automatique que j'ai utilisé est l'indentation automatique d'emacs.
Carson Myers

2
Les formateurs de code source peuvent être contraints avec un simple // après chaque appel de méthode dans la chaîne. Cela affaiblit un peu votre code, mais pas autant que votre série verticale d'instructions soit reformatée horizontalement.
qualidafial

@qualidafial Vous n'avez pas besoin //après chaque méthode si vous configurez votre IDE pour ne pas joindre les lignes déjà encapsulées (par exemple Eclipse> Propriétés> Java> Style de code> Formateur> Line Wrapping> Ne jamais joindre les lignes déjà encapsulées).
DJDaveMark

6

Ce schéma (jeu de mots voulu), appelé «interface fluide», devient très populaire maintenant. C'est acceptable, mais ce n'est pas vraiment ma tasse de thé.


6

Parce qu'il ne retourne pas void, ce n'est plus un setter de propriétés JavaBean valide. Cela peut être important si vous êtes l'une des sept personnes dans le monde à utiliser les outils visuels "Bean Builder", ou l'une des 17 à l'aide des éléments JSP-bean-setProperty.


Cela est également important si vous utilisez des frameworks compatibles avec le bean comme Spring.
ddimitrov

6

Au moins en théorie , cela peut endommager les mécanismes d'optimisation de la JVM en définissant de fausses dépendances entre les appels.

Il est censé être du sucre syntaxique, mais peut en fait créer des effets secondaires dans la machine virtuelle du Java 43 super intelligent.

C'est pourquoi je vote non, ne l'utilisez pas.


10
Intéressant ... pourriez-vous développer un peu cela?
Ken Liu

3
Pensez simplement à la façon dont les processeurs superscalaires gèrent l'exécution parallèle. L'objet sur lequel exécuter la deuxième setméthode dépend de la première setméthode bien qu'il soit connu du programmeur.
Marian

2
Je ne suis toujours pas. Si vous définissez Foo puis Bar avec deux instructions distinctes, l'objet pour lequel vous définissez Bar a un état différent de celui pour lequel vous définissez Foo. Le compilateur n'a donc pas pu paralléliser ces instructions non plus. Au moins, je ne vois pas comment cela pourrait sans introduire une hypothèse injustifiée. (Puisque je n'en ai aucune idée, je ne nierai pas que Java 43 fait en fait la parallélisation dans le premier cas mais pas dans l'autre et introduit l'hypothèse injustifiée dans un cas mais pas dans l'autre).
masonk

12
Si vous ne savez pas, testez. -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Le jdk java7 intègre définitivement les méthodes chaînées, et fait environ le même nombre d'itérations qu'il faut pour marquer les setters vides comme chauds et les aligner aussi. Il me semble que vous sous-estimez la puissance des algorithmes d'élagage d'opcode de la JVM; s'il sait que vous retournez ceci, il sautera l'opcode jrs (java return statement) et le laissera simplement sur la pile.
Ajax

1
Andreas, je suis d'accord mais des problèmes apparaissent lorsque vous avez couche après couche de code inefficace. 99% du temps, vous devez coder pour plus de clarté, ce qui est souvent dit ici. Mais il y a aussi des moments où vous devez être réaliste et utiliser vos années d'expérience pour optimiser prématurément dans un sens architectural général.
LegendLength

6

Ce n'est pas du tout une mauvaise pratique. Mais ce n'est pas compatible avec JavaBeans Spec .

Et il y a beaucoup de spécifications qui dépendent de ces accesseurs standard.

Vous pouvez toujours les faire coexister les uns avec les autres.

public class Some {
    public String getValue() { // JavaBeans
        return value;
    }
    public void setValue(final String value) { // JavaBeans
        this.value = value;
    }
    public String value() { // simple
        return getValue();
    }
    public Some value(final String value) { // fluent/chaining
        setValue(value);
        return this;
    }
    private String value;
}

Maintenant, nous pouvons les utiliser ensemble.

new Some().value("some").getValue();

Voici une autre version pour objet immuable.

public class Some {

    public static class Builder {

        public Some build() { return new Some(value); }

        public Builder value(final String value) {
            this.value = value;
            return this;
        }

        private String value;
    }

    private Some(final String value) {
        super();
        this.value = value;
    }

    public String getValue() { return value; }

    public String value() { return getValue();}

    private final String value;
}

Maintenant, nous pouvons le faire.

new Some.Builder().value("value").build().getValue();

2
Ma modification a été rejetée, mais votre exemple Builder n'est pas correct. Tout d'abord, .value () ne renvoie rien et ne définit même pas le somechamp. En second lieu , vous devez ajouter une sauvegarde et un ensemble someà nullen construction () donc Someest vraiment immuable, sinon vous pourriez appeler builder.value()sur la même instance Builder à nouveau. Et enfin, oui, vous avez un constructeur, mais vous avez Sometoujours un constructeur public, ce qui signifie que vous ne préconisez pas ouvertement l'utilisation du constructeur, c'est-à-dire que l'utilisateur n'en sait rien autrement qu'en essayant ou en recherchant une méthode pour définir une coutume valuedu tout.
Adowrath

@Adowrath si la réponse est incorrecte, vous devez écrire votre propre réponse, ne pas essayer de modifier la forme de quelqu'un d'autre
CalvT

1
@JinKwon Great. Merci! Et désolé si j'avais l'air impoli auparavant.
Adowrath

1
@Adowrath N'hésitez pas à nous faire part de vos commentaires supplémentaires concernant les améliorations. Pour info, je ne suis pas celui qui a rejeté votre montage. :)
Jin Kwon

1
Je sais je sais. ^^ Et merci pour la nouvelle version, qui fournit maintenant un vrai constructeur "Immutable-Some" mutable. Et c'est une solution plus intelligente que mes tentatives d'édition qui, en comparaison, encombraient le code.
Adowrath

4

Si vous utilisez la même convention dans toute l'application, cela semble bien.

D'un autre côté, si une partie existante de votre application utilise une convention standard, je m'en tiendrai et ajouterai des constructeurs à des classes plus complexes

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getfat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

        public NutritionalFacts build() {
            return new NutritionalFacts(this);
        }
    }

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

1
C'est exactement ce que le modèle Builder a été conçu pour corriger. Il ne casse pas les lambdas dans Java 8, il ne casse pas les outils JavaBeans capricieux et il ne cause aucun problème d'optimisation dans JVM (puisque l'objet n'existe que pendant l'instanciation). Il résout également le problème «beaucoup trop de constructeurs» que vous obtenez en n'utilisant simplement pas de constructeurs, ainsi qu'en éliminant la pollution par les tas des classes anonymes à double croisillon.
ndm13

Point intéressant - je remarque que si presque rien dans votre classe n'est immuable, par exemple (une interface graphique hautement configurable, par exemple), vous pouvez probablement éviter complètement Builder.
Philip Guin


3

Je suis en faveur du retour des setters. Je m'en fiche si ce n'est pas compatible avec les haricots. Pour moi, si c'est correct d'avoir l'expression / l'instruction "=", alors les setters qui retournent des valeurs sont très bien.


2

J'avais l'habitude de préférer cette approche mais je l'ai décidée.

Les raisons:

  • Lisibilité. Cela rend le code plus lisible pour avoir chaque setFoo () sur une ligne distincte. Vous lisez généralement le code beaucoup, beaucoup plus de fois que la seule fois où vous l'écrivez.
  • Effet secondaire: setFoo () ne devrait définir que le champ foo, rien d'autre. Renvoyer ceci est un extra "QU'EST-CE QUE c'était".

Le modèle Builder que j'ai vu n'utilise pas la convention setFoo (foo) .setBar (bar) mais plus foo (foo) .bar (bar). Peut-être exactement pour ces raisons.

C'est, comme toujours, une question de goût. J'aime bien l'approche des "moindres surprises".


2
Je suis d'accord sur l'effet secondaire. Les colons qui retournent des trucs violent leur nom. Vous mettez foo, mais vous récupérez un objet? Est-ce un nouvel objet ou ai-je modifié l'ancien?
crunchdog

2

Ce modèle particulier est appelé chaînage de méthodes. Lien Wikipédia , cela a plus d'explications et d'exemples sur la façon dont cela se fait dans divers langages de programmation.

PS: J'ai juste pensé à le laisser ici, car je cherchais le nom spécifique.


1

À première vue: "horrible!".

Après réflexion

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

est en fait moins sujet aux erreurs que

Employee anEmployee = new Employee();
anEmployee.setName("xxx");
...
list.add(anEmployee);

Tellement intéressant. Ajout d'une idée à la trousse à outils ...


1
Non, c'est toujours horrible. Du point de vue de la maintenance, ce dernier est meilleur car il est plus facile à lire. De plus, les vérificateurs de code automatisés comme CheckStyle appliqueront les lignes à 80 caractères par défaut - le code serait de toute façon encapsulé, aggravant le problème de lisibilité / maintenance. Et enfin - c'est Java; il n'y a aucun avantage à tout écrire sur une seule ligne quand il sera de toute façon compilé en octet.
OMG Ponies

1
personnellement, je pense que le premier est plus facile à lire, surtout si vous créez plusieurs objets de cette façon.
Ken Liu

@Ken: codez une méthode. Écrivez une copie dans un format fluide; une autre copie dans l'autre. Donnez maintenant les deux copies à quelques personnes et demandez laquelle elles trouvent plus facile à lire. Plus rapide à lire, plus rapide à coder.
OMG Ponies

Comme la plupart des outils, il peut être facile d'en abuser. JQuery est orienté autour de cette technique et est donc sujet à de longues chaînes d'appels qui, selon moi, nuisent en fait à la lisibilité.
staticsan

2
Ce serait bien s'il y avait des sauts de ligne avant chaque point et en retrait. Ensuite, ce serait aussi lisible que la deuxième version. En fait plus, car il n'y a pas de redondance listau départ.
MauganRa

1

Oui, je pense que c'est une bonne idée.

Si je pouvais ajouter quelque chose, qu'en est-il de ce problème:

class People
{
    private String name;
    public People setName(String name)
    {
        this.name = name;
        return this;
    }
}

class Friend extends People
{
    private String nickName;
    public Friend setNickName(String nickName)
    {
        this.nickName = nickName;
        return this;
    }
}

Cela fonctionnera:

new Friend().setNickName("Bart").setName("Barthelemy");

Cela ne sera pas accepté par Eclipse! :

new Friend().setName("Barthelemy").setNickName("Bart");

En effet, setName () renvoie un People et non un Friend, et il n'y a pas PeoplesetNickName.

Comment pourrions-nous écrire des setters pour renvoyer la classe SELF au lieu du nom de la classe?

Quelque chose comme ça serait bien (si le mot-clé SELF existait). Est-ce que cela existe de toute façon?

class People
{
    private String name;
    public SELF setName(String name)
    {
        this.name = name;
        return this;
    }
}

1
Il y a quelques autres compilateurs Java en plus d'Eclipse qui n'accepteront pas cela :). Fondamentalement, vous exécutez des volailles du système de type Java (qui est statique, pas dynamique comme certains langages de script): vous devrez convertir ce qui sort de setName () en un ami avant de pouvoir y définirNickName (). Donc, malheureusement, pour les hiérarchies d'héritage, cela élimine une grande partie de l'avantage de lisibilité et rend cette technique autrement potentiellement utile moins utile.
Cornel Masson

5
Utilisez des génériques. class Chainable <Self étend Chainable> {public Self doSomething () {return (Self) this;}} Ce n'est pas techniquement sûr pour le type (il sera converti en classe si vous implémentez la classe avec un type Self, vous ne pouvez pas le faire), mais il est une grammaire correcte et les sous-classes renverront leur propre type.
Ajax

De plus: Ce "SELF" que Baptiste utilise est appelé un type propre, pas présent dans de nombreuses langues (Scala est vraiment le seul auquel je peux penser en ce moment), où comme l'utilisation par Ajax des génériques est ce qu'on appelle "Curieusement" modèle de modèle récurrent ", qui essaie de faire face à un manque de type auto, mais il présente certains inconvénients: (de class C<S extends C<S>>, ce qui est plus sûr qu'un simple S extends C. a) Si A extends B, et B extends C<B>, et vous avez un A, vous savez seulement qu'un B est renvoyé à moins que A ne remplace chaque méthode. b) Vous ne pouvez pas désigner un local, non brut C<C<C<C<C<...>>>>>.
Adowrath

1

En général, c'est une bonne pratique, mais pour les fonctions de type ensemble, vous devrez peut-être utiliser le type booléen pour déterminer si l'opération s'est terminée avec succès ou non, c'est une façon également. En général, il n'y a pas de dogme pour dire que c'est bon ou lit, ça vient de la situation, bien sûr.


2
Que diriez-vous d'utiliser des exceptions pour indiquer une condition d'erreur? Les codes d'erreur peuvent être facilement ignorés comme de nombreux programmeurs C l'ont douloureusement appris. Les exceptions peuvent remonter la pile au point où elles peuvent être gérées.
ddimitrov

Les exceptions sont généralement préférables, mais les codes d'erreur sont également utiles lorsque vous ne pouvez pas utiliser d'exceptions.
Narek

1
Bonjour @Narek, peut-être pourriez-vous préciser dans quels cas il est préférable d'utiliser des codes d'erreur dans les setters, plutôt que des exceptions et pourquoi?
ddimitrov

0

De la déclaration

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

je vois deux choses

1) Déclaration vide de sens. 2) Manque de lisibilité.


0

Cela peut être moins lisible

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!")); 

ou ca

list.add(new Employee()
          .setName("Jack Sparrow")
          .setId(1)
          .setFoo("bacon!")); 

C'est bien plus lisible que:

Employee employee = new Employee();
employee.setName("Jack Sparrow")
employee.setId(1)
employee.setFoo("bacon!")); 
list.add(employee); 

8
Je pense que c'est assez lisible si vous n'essayez pas de mettre tout votre code sur une seule ligne.
Ken Liu

0

Je fais mes setters depuis un bon moment et le seul vrai problème est avec les bibliothèques qui collent avec les getPropertyDescriptors stricts pour obtenir les accesseurs de bean reader / writer bean. Dans ces cas, votre "bean" java n'aura pas les écrivains que vous attendez.

Par exemple, je ne l'ai pas testé à coup sûr, mais je ne serais pas surpris que Jackson ne les reconnaisse pas comme des setters lors de la création d'objets Java à partir de json / maps. J'espère que je me trompe sur celui-ci (je le testerai bientôt).

En fait, je développe un ORM SQL centré léger et je dois ajouter du code au-delà de getPropertyDescriptors aux setters reconnus qui le retournent.


0

Il y a longtemps, répondez, mais mes deux cents ... C'est bien. Je souhaite que cette interface fluide soit utilisée plus souvent.

La répétition de la variable «usine» n'ajoute pas plus d'informations ci-dessous:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Foo.class);
factory.setFilter(new MethodFilter() { ...

C'est plus propre, à mon humble avis:

ProxyFactory factory = new ProxyFactory()
.setSuperclass(Properties.class);
.setFilter(new MethodFilter() { ...

Bien sûr, comme l'une des réponses déjà mentionnées, l'API Java devrait être modifiée pour le faire correctement dans certaines situations, comme l'héritage et les outils.


0

Il est préférable d'utiliser d'autres constructions de langage si elles sont disponibles. Par exemple, dans Kotlin, vous utilisez avec , appliquez ou laissez . Si vous utilisez cette approche, vous n'aurez pas vraiment besoin de renvoyer une instance de votre setter.

Cette approche permet à votre code client d'être:

  • Indifférent au type de retour
  • Plus facile à entretenir
  • Évitez les effets secondaires du compilateur

Voici quelques exemples.

val employee = Employee().apply {
   name = "Jack Sparrow"
   id = 1
   foo = "bacon"
}


val employee = Employee()
with(employee) {
   name = "Jack Sparrow"
   id = 1
   foo = "bacon"
}


val employee = Employee()
employee.let {
   it.name = "Jack Sparrow"
   it.id = 1
   it.foo = "bacon"
}

0

Si j'écris une API, j'utilise "return this" pour définir des valeurs qui ne seront définies qu'une seule fois. Si j'ai d'autres valeurs que l'utilisateur devrait pouvoir changer, j'utilise plutôt un setter vide standard.

Cependant, c'est vraiment une question de préférence et le chaînage des setters a l'air plutôt cool, à mon avis.


0

Je suis d'accord avec toutes les affiches affirmant que cela rompt avec les spécifications JavaBeans. Il y a des raisons de préserver cela, mais je pense également que l'utilisation de ce modèle de générateur (auquel il a été fait allusion) a sa place; tant qu'il n'est pas utilisé partout, il doit être acceptable. "It's Place", pour moi, est l'endroit où le point final est un appel à une méthode "build ()".

Il existe bien sûr d'autres façons de définir toutes ces choses, mais l'avantage ici est qu'il évite 1) les constructeurs publics à plusieurs paramètres et 2) les objets partiellement spécifiés. Ici, le générateur collecte ce qui est nécessaire, puis appelle son "build ()" à la fin, ce qui peut garantir qu'un objet partiellement spécifié n'est pas construit, car cette opération peut bénéficier d'une visibilité inférieure à la visibilité publique. L'alternative serait des "objets paramètres", mais cette IMHO ne fait que repousser le problème d'un niveau.

Je n'aime pas les constructeurs à plusieurs paramètres car ils rendent plus probable la transmission de nombreux arguments de même type, ce qui peut faciliter la transmission des mauvais arguments aux paramètres. Je n'aime pas utiliser beaucoup de setters car l'objet pourrait être utilisé avant qu'il ne soit entièrement configuré. De plus, la notion d'avoir des valeurs par défaut basées sur des choix précédents est mieux servie avec une méthode "build ()".

En bref, je pense que c'est une bonne pratique, si elle est utilisée correctement.


-4

Mauvaise habitude: un setter définit un getter get

qu'en déclarant explicitement une méthode, cela le fait pour U

setPropertyFromParams(array $hashParamList) { ... }

pas bon pour l'auto-complétion et le refactoring et la lisibilité et le code passe-partout
Andreas Dietrich

Parce que: 1) vous devez vous souvenir de l'ordre ou le lire dans la documentation, 2) vous perdez toute sécurité de type à la compilation, 3) vous devez transtyper les valeurs (ceci et 2) n'est bien sûr pas un problème dans une dynamique bien sûr), 4) vous ne pouvez pas étendre cela utilement autre que la vérification de la longueur du tableau à chaque appel, et 5) si vous changez quelque chose, que ce soit l'ordre ou même l'existence de certaines valeurs, vous ne pouvez pas vérifier que ni l'exécution ni compiler timewithout doute du tout . Avec les setters: 1), 2), 3): Pas de problème. 4) L'ajout de nouvelles méthodes ne casse rien. 5) message d'erreur explicite.
Adowrath
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.