web-dev-qa-db-fra.com

Est-ce une mauvaise pratique de faire en sorte que le setter retourne "this"?

Est-ce une bonne ou une mauvaise idée de demander aux installateurs de Java de retourner "this"?

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

Ce modèle peut être utile car vous pouvez alors enchaîner les 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 en valoir la peine, simplement parce que cela peut amener le programmeur à faire autre chose d’utile. J'ai vu ce modèle utiliser certains endroits (JMock, JPA, par exemple), mais il semble peu commun 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 quelques réflexions sur le point de savoir si cela est généralement acceptable et s'il existe des pièges ou des pratiques optimales connexes. Je connais le modèle Builder, mais il est un peu plus compliqué que ce que je décris - comme le décrit Josh Bloch, il existe une classe Builder statique associée à la création d'objets.

220
Ken Liu

Je ne pense pas qu'il y ait quelque chose qui cloche avec ça, c'est juste une question de style. C'est utile quand:

  • 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 y a beaucoup de combinaisons différentes pour quels champs vous voulez définir.

Les alternatives à cette méthode pourraient être:

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

Si vous ne définissez que quelques propriétés à la fois, je dirais que cela ne vaut pas la peine de rendre «this». Si vous décidez ultérieurement de renvoyer quelque chose d'autre, tel qu'un indicateur de statut/de réussite/un message, il tombe certainement.

68
Tom Clift

Ce n'est pas une mauvaise pratique. C'est une pratique de plus en plus courante. La plupart des langues ne vous obligent pas à traiter l'objet renvoyé si vous ne le souhaitez pas. Ainsi, la syntaxe d'utilisation "normale" du setter ne change pas, mais vous permet d'enchaîner les setters.

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

C'est aussi courant dans l'API Java:

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

Résumer:

  • cela s'appelle une "interface fluide", ou "chaînage de méthodes".
  • ce n'est pas "standard" Java, bien que vous le voyiez de plus en plus de nos jours (fonctionne très bien dans jQuery)
  • il enfreint la spécification JavaBean, il va donc rompre avec divers outils et bibliothèques, en particulier les générateurs JSP et Spring.
  • cela peut empêcher certaines optimisations normalement effectuées par la machine virtuelle
  • certaines personnes pensent qu'il nettoie le code, d'autres le trouvent "horrible"

Quelques autres points non mentionnés:

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

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

  • Enfin, voici un point de données du monde réel. J'ai eu des problèmes pour utiliser une bibliothèque construite comme celle-ci. Le constructeur 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);
    
  • Cela 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 que la méthode en renvoie un nouveau. Cela rend simplement la bibliothèque plus difficile à utiliser, et de nombreux programmeurs n'exploitent pas cette fonctionnalité. Si les setters étaient des setters, il serait plus clair de l'utiliser.

76
ndp

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;
}

Ainsi:

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

Si vous ne souhaitez pas renvoyer 'this' à partir du paramètre mais ne souhaitez 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 aparté je pense que c'est légèrement plus propre en C #:

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

Non seulement il brise la convention des getters/setters, mais il rompt également le cadre de référence de la méthode Java 8. MyClass::setMyValue est un BiConsumer<MyClass,MyValue> et myInstance::setMyValue est un Consumer<MyValue>. Si vous avez votre setter return this, alors ce n'est plus une instance valide de Consumer<MyValue>, mais plutôt un Function<MyValue,MyClass>, et tout ce qui utilise des références de méthode à ces setters (en supposant qu'il s'agisse de méthodes vides) se rompt.

9
Steve K

Je ne connais pas Java, mais j’ai fait cela en C++ . D’autres personnes ont déjà dit que cela rendait les lignes très longues et très difficiles à lire, Mais je l’ai fait comme cela bien des 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!"));

au moins, je pense. Mais vous êtes le bienvenu pour m'abaisser et m'appeler un programmeur terrible si vous le souhaitez. Et je ne sais pas si vous êtes autorisé à le faire même en Java.

7
Carson Myers

Comme il ne retourne pas void, ce n'est plus un configurateur de propriétés JavaBean valide. Cela pourrait avoir de l'importance si vous êtes l'une des sept personnes au monde utilisant des outils visuels "Générateur de haricots" ou l'un des 17 utilisant des éléments JSP-bean-setProperty.

6
Ken

Au moinsen théorie, il peut endommager les mécanismes d'optimisation de la machine virtuelle en définissant de fausses dépendances entre les appels.

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

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

5
Marian

Ce n'est pas une mauvaise pratique du tout. 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.

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();
5
Jin Kwon

Ce système (appelé "jeu de mots"), appelé "interface fluide", est en train de devenir populaire. C'est acceptable, mais ce n'est pas vraiment ma tasse de thé.

5
Noon Silk

Je suis en faveur des setters ayant "ce" retourne. Je me fiche de savoir si ce n'est pas conforme aux fèves. Pour moi, s'il est correct d'avoir l'expression/la déclaration "=", alors les paramètres qui renvoient des valeurs conviennent.

3
Monty Hall

Paulo Abrantes offre un autre moyen de rendre les décodeurs JavaBean fluides: définissez une classe de constructeur interne pour chaque JavaBean. Si vous utilisez des outils qui vous effraient par des paramètres qui renvoient des valeurs, le modèle de Paulo pourrait vous aider.

3
Jim Ferrans

Si vous utilisez la même convention dans son intégralité, cela semble aller.

D'un autre côté, si la partie existante de votre application utilise la convention standard, je la respecterais et ajouterais des générateurs à des classes plus compliquées.

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;
    }
}
2
Marcin Szymczak

Ce modèle particulier s'appelle Method Chaining. Lien Wikipedia , il contient davantage d’explications et d’exemples sur la manière de procéder dans divers langages de programmation.

P.S: Je viens de penser à le laisser ici, puisque je cherchais le nom spécifique.

2
capt.swag

J'avais l'habitude de préférer cette approche mais j'ai décidé de ne pas la prendre.

Les raisons:

  • Lisibilité. Cela rend le code plus lisible d'avoir chaque setFoo () sur une ligne séparée. Vous lisez habituellement le code plusieurs fois, beaucoup plus souvent 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 "QUOI était-ce".

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

C'est, comme toujours, une question de goût. J'aime juste l'approche "moins de surprises".

2

À 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 boîte à outils ...

1
djna

En général, c’est une bonne pratique, mais vous aurez peut-être besoin que les fonctions de type set utilisent le type booléen pour déterminer si l’opération s’est terminée avec succès ou non, c’est un moyen é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.

1
Narek

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");

C'est parce que setName () renvoie un peuple et non un ami et qu'il n'existe aucun PeoplesetNickName.

Comment pourrions-nous écrire aux setters de retourner la classe SELF au lieu du nom de la classe?

Quelque chose comme ce serait bien (si le mot-clé SELF existait). Est-ce que ça existe quand même?

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

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

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

Je n'aime pas les constructeurs à paramètres multiples, car ils rendent plus probable la transmission d'un grand nombre d'arguments de même type, ce qui peut faciliter la transmission des mauvais arguments aux paramètres. Je n'aime pas utiliser beaucoup de paramètres, car l'objet peut être utilisé avant qu'il ne soit entièrement configuré. En outre, la notion de "build ()" sert mieux les valeurs par défaut basées sur les choix précédents.

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

0
Javaneer

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 beaucoup plus lisible que:

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

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

Cependant, c'est vraiment une question de préférence et l'enchaînement des régleurs a l'air plutôt cool, à mon avis.

0
Kaiser Keister

De la déclaration

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

je vois deux choses

1) Déclaration dénuée de sens. 2) Manque de lisibilité.

0
Madhu

Je fabrique mes setters depuis un certain temps et le seul problème concerne les bibliothèques qui s’appliquent strictement aux getPropertyDescriptors pour obtenir les accesseurs de bean reader/writer bean. Dans ces cas, votre "haricot" Java n'aura pas les écritures que vous attendez. 

Par exemple, je ne l’ai pas testé avec certitude, mais je ne serais pas surpris que Jackson ne les reconnaisse pas comme des paramètres 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 léger et centré sur SQL et je dois ajouter du code au-delà de getPropertyDescriptors à des contrôleurs reconnus qui le renvoient. 

0
Jeremy Chone

Il y a longtemps de répondre, mais mes deux cents ... C'est bien. J'aimerais que cette interface fluide soit utilisée plus souvent.

La répétition de la variable 'factory' n'apporte 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 entendu, comme l’une des réponses déjà évoquées, l’API Java devrait être modifiée pour pouvoir le faire correctement dans certaines situations, comme l’héritage et les outils.

0
Josef.B

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

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

  • Indifférent au type de retour
  • Plus facile à maintenir
  • Eviter 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
Steven Spungin