È una cattiva pratica fare in modo che un setter ritorni "questo"?


249

È una buona o cattiva idea fare in modo che i setter di java ritornino "questo"?

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

Questo modello può essere utile perché in questo modo è possibile concatenare setter come questo:

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

Invece di questo:

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

... ma in qualche modo va contro la convenzione standard. Suppongo che potrebbe valere la pena solo perché può rendere quel setter fare qualcos'altro utile. Ho visto che questo modello utilizzava alcuni punti (ad esempio JMock, JPA), ma sembra insolito e generalmente utilizzato solo per API molto ben definite in cui questo modello viene utilizzato ovunque.

Aggiornare:

Ciò che ho descritto è ovviamente valido, ma quello che sto davvero cercando sono alcuni pensieri sul fatto che questo sia generalmente accettabile e se ci sono insidie ​​o buone pratiche correlate. Conosco il modello Builder, ma è un po 'più coinvolto di quello che sto descrivendo - poiché Josh Bloch lo descrive, esiste una classe Builder statica associata per la creazione di oggetti.


1
Da quando ho visto questo modello di design qualche tempo fa, lo faccio ovunque possibile. Se un metodo non ha esplicitamente bisogno di restituire qualcosa per fare il suo lavoro, ora ritorna this. A volte, modifico anche la funzione in modo che invece di restituire un valore, funzioni su un membro dell'oggetto, solo così posso farlo. È meraviglioso. :)
Inversus,

4
Per i setter telescopici che ritornano da soli e nei costruttori preferisco usare withName (String name) invece di setName (String name). Come hai sottolineato una pratica comune e l'aspettativa per il setter è di restituire il vuoto. Setter "non standard" non possono comportarsi bene con i quadri esistenti ad esempio JPA gestori di entità, Primavera, ecc
oᴉɹǝɥɔ

Si prega di introdurre le interruzioni di riga prima di ogni chiamata :) E configurare il proprio IDE o ottenerne uno corretto se non lo rispetta.
MauganRa,

Le strutture ampiamente utilizzate (ad esempio Spring e Hibernate) aderiranno rigorosamente (almeno una volta) alla convenzione dei vuoti-setter
Legna,

Risposte:


83

Non penso che ci sia qualcosa di specificamente sbagliato in questo, è solo una questione di stile. È utile quando:

  • Devi impostare più campi contemporaneamente (anche in fase di costruzione)
  • sai quali campi devi impostare nel momento in cui stai scrivendo il codice e
  • ci sono molte combinazioni diverse per quali campi vuoi impostare.

Le alternative a questo metodo potrebbero essere:

  1. Un mega costruttore (svantaggio: potresti passare molti valori nulli o predefiniti e diventa difficile sapere quale valore corrisponde a cosa)
  2. Diversi costruttori sovraccarichi (aspetto negativo: diventa ingombrante quando ne hai più di alcuni)
  3. Metodi di fabbrica / statici (aspetto negativo: uguale ai costruttori sovraccarichi - diventa ingombrante quando ce ne sono più di alcuni)

Se hai intenzione di impostare solo poche proprietà alla volta, direi che non vale la pena restituire 'questo'. Cade sicuramente se in seguito decidi di restituire qualcos'altro, come un indicatore / messaggio di stato / successo.


1
bene, generalmente non si restituisce nulla da un setter, per convenzione.
Ken Liu,

17
Forse non per cominciare, ma un setter non mantiene necessariamente il suo scopo originale. Quella che era una variabile poteva cambiare in uno stato che comprende diverse variabili o avere altri effetti collaterali. Alcuni setter potrebbero restituire un valore precedente, altri potrebbero restituire un indicatore di errore se l'errore è troppo comune per un'eccezione. Ciò solleva tuttavia un altro punto interessante: cosa succede se uno strumento / framework che stai utilizzando non riconosce i tuoi setter quando hanno valori di ritorno?
Tom Clift,

12
@Tom buon punto, fare questo rompe la convenzione "Java bean" per getter e setter.
Andy White,

2
@TomClift Rompere la convenzione "Java Bean" causa problemi? Sono librerie che utilizzano la convenzione "Java Bean" guardando il tipo restituito o solo i parametri del metodo e il nome del metodo.
Theo Briscoe,

3
questo è il motivo per cui esiste il modello Builder, i setter non dovrebbero restituire qualcosa, invece, creare un builder se il suo bisogno sembra migliore e richiede meno codice :)
RicardoE

106

Non è una cattiva pratica. È una pratica sempre più comune. La maggior parte delle lingue non richiede di trattare l'oggetto restituito se non lo si desidera, quindi non cambia la sintassi "normale" di utilizzo del setter ma consente di concatenare i setter insieme.

Questo è comunemente chiamato un modello di generatore o un'interfaccia fluida .

È anche comune nell'API Java:

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

26
È spesso usato nei costruttori, ma non direi "questo è ... chiamato modello Builder".
Laurence Gonsalves il

10
È divertente per me che alcune delle motivazioni alla base delle interfacce fluide sia che rendono il codice più facile da leggere. Potrei vedere che è più conveniente scrivere, ma mi sembra un po 'più difficile da leggere. Questo è l'unico vero disaccordo che ho con esso.
Brent scrive il codice

30
È anche noto come antipasto del disastro ferroviario. Il problema è che quando una traccia dello stack di eccezioni con puntatore null contiene una riga come questa, non si ha idea di quale invocazione abbia restituito null. Questo non vuol dire che il concatenamento dovrebbe essere evitato a tutti i costi, ma attenzione alle cattive librerie (specialmente a casa).
ddimitrov

18
@ddimitrov aslong come si limita a tornando questo non sarebbe mai stato un problema (solo la prima invocazione potrebbe gettare NPE)
Stefan

4
è più facile scrivere E leggere supponendo che tu abbia messo interruzioni di riga e rientri in cui altrimenti la leggibilità subirebbe! (perché evita ingombri ridondanti di codice ripetuto come bla.foo.setBar1(...) ; bla.foo.setBar2(...)quando si potrebbe scrivere bla.foo /* newline indented */.setBar1(...) /* underneath previous setter */ .setBar2(...)(non è possibile utilizzare le interruzioni di riga in un commento SO come questo :-( ... spero che tu ottenga il punto considerando 10 di questi setter o chiamate più complesse)
Andreas Dietrich

90

Riassumere:

  • si chiama "interfaccia fluente", o "metodo concatenamento".
  • questo non è Java "standard", anche se in questi giorni lo vedi di più (funziona benissimo in jQuery)
  • viola le specifiche JavaBean, quindi si romperà con vari strumenti e librerie, in particolare i costruttori JSP e Spring.
  • potrebbe impedire alcune ottimizzazioni che la JVM farebbe normalmente
  • alcune persone pensano che ripulisca il codice, altri pensano che sia "orribile"

Un paio di altri punti non menzionati:

  • Ciò viola il principio secondo cui ogni funzione dovrebbe fare una (e una sola) cosa. Puoi o non puoi crederci, ma in Java credo che funzioni bene.

  • Gli IDE non li genereranno per te (per impostazione predefinita).

  • Finalmente, ecco un punto dati del mondo reale. Ho avuto problemi con l'utilizzo di una libreria costruita in questo modo. Il generatore di query di Hibernate ne è un esempio in una libreria esistente. Poiché i metodi set * di Query stanno restituendo query, è impossibile dire semplicemente guardando la firma come usarla. Per esempio:

    Query setWhatever(String what);
  • Presenta un'ambiguità: il metodo modifica l'oggetto corrente (il tuo modello) o, forse, Query è davvero immutabile (un modello molto popolare e prezioso) e il metodo ne sta restituendo uno nuovo. Rende solo la libreria più difficile da usare e molti programmatori non sfruttano questa funzione. Se i setter fossero setter, sarebbe più chiaro come usarlo.


5
a proposito, è "fluente", non "fluido" ... in quanto consente di strutturare una serie di chiamate di metodo come una lingua parlata.
Ken Liu,

1
L'ultimo punto sull'immutabilità è molto importante. L'esempio più semplice è String. Gli sviluppatori Java si aspettano che quando utilizzano i metodi su String ottengano un'istanza totalmente nuova e non la stessa ma modificata. Con un'interfaccia fluida, nella documentazione del metodo dovrebbe essere indicato che l'oggetto restituito è "questo" anziché una nuova istanza.
MeTTeO,

7
Anche se concordo nel complesso, non sono d'accordo sul fatto che ciò viola il principio "fai solo una cosa". Il ritorno thisè una scienza missilistica difficilmente complessa. :-)
user949300

punto aggiuntivo: viola anche il principio di separazione Comando-query.
Marton_hun,

84

Preferisco usare i metodi 'with' per questo:

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

Così:

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

Avvertenza : questa withXsintassi viene comunemente utilizzata per fornire "setter" per oggetti immutabili, quindi i chiamanti di questi metodi potrebbero ragionevolmente aspettarsi che creino nuovi oggetti anziché mutare l'istanza esistente. Forse una formulazione più ragionevole sarebbe qualcosa del tipo:

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

Con la convenzione di denominazione chainsetXyz () praticamente tutti dovrebbero essere felici.


18
+1 Per convenzione interessante. Non lo adotterò nel mio codice poiché sembra che ora devi avere un campo a get, a sete a withper ogni classe. È comunque una soluzione interessante. :)
Paul Manta,

1
Dipende da quanto spesso chiami i setter. Ho scoperto che se quei setter vengono chiamati molto, vale la pena di aggiungerli perché semplifica il codice ovunque. YMMV
qualidafial

2
E se si aggiunge che questo Project Lombok's @Getter/@Setterannotazioni ... che sarebbe fantastico per il concatenamento. Oppure potresti usare qualcosa di molto simile al Kestrelcombinatore ( github.com/raganwald/Katy ) usato dai demoni di JQuery e Javascript.
Ehtesh Choudhury,

4
@ AlikElzin-kilaka In realtà ho appena notato che le classi immutabili java.time in Java 8 usano questo schema, ad esempio LocalDate.withMonth, withYear, ecc.
Qualidafial

4
il withprefisso è una convenzione diversa. come @qualidafial ha dato un esempio. i metodi con il prefisso withnon dovrebbero restituire thisma piuttosto una nuova istanza come l'istanza corrente ma withquella modifica. Questo viene fatto quando vuoi che i tuoi oggetti siano immutabili. Quindi quando vedo un metodo con il prefisso withpresumo che otterrò un nuovo oggetto, non lo stesso oggetto.
tempcke

26

Se non si desidera tornare 'this'dal setter ma non si desidera utilizzare la seconda opzione, è possibile utilizzare la sintassi seguente per impostare le proprietà:

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

A parte questo penso che sia leggermente più pulito in C #:

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

16
l'inizializzazione della doppia parentesi graffa può avere problemi con uguali perché crea una classe interna anonima; vedi c2.com/cgi/wiki?DoubleBraceInitialization
Csaba_H

@Csaba_H Chiaramente quel problema è colpa della persona che ha pasticciato il equalsmetodo. Ci sono modi molto chiari per gestire le lezioni anonime equalsse sai cosa stai facendo.
AJMansfield,

creare una nuova classe (anonima) solo per quello? per ogni istanza?
user85421

11

Non solo rompe la convenzione di getter / setter, ma rompe anche il framework di riferimento del metodo Java 8. MyClass::setMyValueè a BiConsumer<MyClass,MyValue>ed myInstance::setMyValueè a Consumer<MyValue>. Se hai il ritorno del setter this, allora non è più un'istanza valida di Consumer<MyValue>, ma piuttosto una Function<MyValue,MyClass>, e causerà l'interruzione di qualsiasi cosa usando i riferimenti di metodo a quei setter (supponendo che siano metodi nulli).


2
Sarebbe fantastico se Java avesse un modo per sovraccaricare in base al tipo di ritorno, non solo alla JVM. Potresti bypassare facilmente molte di queste modifiche.
Adowrath,

1
È sempre possibile definire un'interfaccia funzionale che si estende sia Consumer<A>e Function<A,B>fornendo un'implementazione predefinita di void accept(A a) { apply(a); }. Quindi può essere facilmente utilizzato come uno dei due e non romperà alcun codice che richiede un modulo specifico.
Steve K,

Argh! Questo è semplicemente sbagliato! Questo si chiama compatibilità del vuoto. Un setter che restituisce un valore può agire come consumatore. ideone.com/ZIDy2M
Michael,

8

Non conosco Java ma l'ho fatto in C ++. Altre persone hanno detto che rende le righe davvero lunghe e davvero difficili da leggere, ma l'ho fatto così tante volte:

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

Questo è ancora meglio:

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

almeno, penso. Ma sei libero di votarmi e chiamarmi un programmatore terribile se lo desideri. E non so se ti è permesso farlo anche in Java.


Il "ancora meglio" non si presta bene alla funzionalità del codice sorgente formato disponibile nei moderni IDE. Sfortunatamente.
Thorbjørn Ravn Andersen,

probabilmente hai ragione ... L'unico formattatore automatico che ho usato è il rientro automatico di emacs.
Carson Myers,

2
I formattatori del codice sorgente possono essere forzati con un semplice // dopo ogni chiamata di metodo nella catena. Brucia un po 'il tuo codice, ma non tanto avendo le tue serie verticali di istruzioni riformattate orizzontalmente.
Qualidafial

@qualidafial //Dopo ogni metodo non è necessario se si configura l'IDE per non unire le linee già tratteggiate (ad es. Eclipse> Proprietà> Java> Stile codice> Formatter> Ritorno a capo > Non unire mai le linee già avvolte).
DJDaveMark,

6

Questo schema (gioco di parole intenzionale), chiamato "interfaccia fluente", sta diventando abbastanza popolare ora. È accettabile, ma non è davvero la mia tazza di tè.


6

Poiché non restituisce nulla, non è più un setter di proprietà JavaBean valido. Ciò potrebbe importare se sei una delle sette persone al mondo che utilizza strumenti visivi "Bean Builder" o una delle 17 persone che usano elementi JSP-bean-setProperty.


È importante anche se si utilizzano framework compatibili con bean come Spring.
ddimitrov,

6

Almeno in teoria , può danneggiare i meccanismi di ottimizzazione della JVM impostando false dipendenze tra le chiamate.

Dovrebbe essere zucchero sintattico, ma in realtà può creare effetti collaterali nella macchina virtuale super intelligente di Java 43.

Ecco perché voto no, non lo uso.


10
Interessante ... potresti approfondire un po 'questo?
Ken Liu,

3
Basti pensare a come i processori superscalari gestiscono l'esecuzione parallela. L'oggetto su cui eseguire il secondo setmetodo dipende dal primo setmetodo sebbene sia noto al programmatore.
Marian,

2
Non seguo ancora. Se imposti Foo e poi Bar con due istruzioni separate, l'oggetto per cui stai impostando Bar ha uno stato diverso dall'oggetto per cui stai impostando Foo. Quindi neanche il compilatore potrebbe parallelizzare quelle affermazioni. Almeno, non vedo come potrebbe senza introdurre un presupposto ingiustificato (dal momento che non ne ho idea, non nego che Java 43 in effetti faccia la parallelizzazione nel caso una volta ma non nell'altro e introduca l'assunzione ingiustificata in un caso ma non nell'altro).
Masonk,

12
Se non lo sai, prova. -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Java7 jdk incorpora sicuramente i metodi concatenati e fa lo stesso numero di iterazioni necessarie per contrassegnare i setter vuoti come caldi e incorporarli. Mi sembra che sottovaluti la potenza degli algoritmi di potatura opcode della JVM; se sa che lo stai restituendo, salterà il codice operativo jrs (dichiarazione di ritorno java) e lo lascerà nello stack.
Ajax

1
Andreas, sono d'accordo, ma i problemi compaiono quando hai un livello su un livello di codice inefficiente. Il 99% delle volte dovresti programmare per chiarezza che viene detto molto qui. Ma ci sono anche momenti in cui devi essere realistico e usare la tua esperienza pluriennale per ottimizzare prematuramente in senso architettonico generale.
LegendLength

6

Non è affatto una cattiva pratica. Ma non è compatibile con le specifiche JavaBeans .

E ci sono molte specifiche che dipendono da quegli accessori standard.

Puoi sempre farli coesistere tra loro.

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

Ora possiamo usarli insieme.

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

Ecco un'altra versione per l'oggetto immutabile.

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

Ora possiamo farlo.

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

2
La mia modifica è stata rifiutata, ma il tuo esempio di Builder non è corretto. Innanzitutto, .value () non restituisce nulla e non imposta nemmeno il somecampo. In secondo luogo, è necessario aggiungere una protezione e impostarlo somesu nullin build (), quindi Someè veramente immutabile, altrimenti è possibile richiamare builder.value()nuovamente la stessa istanza di Builder. E infine, sì, hai un costruttore, ma hai Someancora un costruttore pubblico, il che significa che non promuovi apertamente l'uso del Costruttore, cioè l'utente non lo conosce se non provando o cercando un metodo per impostare un personalizzato valueaffatto.
Adowrath,

@Adowrath se la risposta non è corretta, dovresti scrivere la tua risposta, non provare a modificare la forma di qualcun altro
CalvT

1
@JinKwon Great. Grazie! E scusami se prima ti sono sembrato maleducato.
Adowrath,

1
@Adowrath Non esitate per eventuali commenti aggiuntivi per miglioramenti. Cordiali saluti, non sono io quello che ha rifiutato la tua modifica. :)
Jin Kwon il

1
Lo so, lo so. ^^ E grazie per la nuova versione, che ora fornisce un vero costruttore "Immutable-Some" mutabile. Ed è una soluzione più intelligente dei miei tentativi di modifica che, al confronto, ingombra il codice.
Adowrath,

4

Se usi la stessa convenzione in tutta l'applicazione, sembra che vada bene.

D'altra parte se la parte esistente della tua applicazione usa una convenzione standard, mi atterrei ad essa e aggiungeremo i costruttori a classi più complicate

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
Questo è esattamente ciò che il modello Builder è stato progettato per risolvere. Non rompe lambdas in Java 8, non rompe gli strumenti complicati di JavaBeans e non causa problemi di ottimizzazione in JVM (poiché l'oggetto esiste solo durante l'istanza). Risolve anche il problema "troppi costruttori" che si ottiene semplicemente non usando i costruttori, nonché eliminando l'inquinamento del mucchio da classi anonime a doppio controvento.
ndm13

Punto interessante: sto notando che se quasi nulla nella tua classe è immutabile, sebbene (ad esempio una GUI altamente configurabile), probabilmente puoi evitare del tutto Builder.
Philip Guin

4

Paulo Abrantes offre un altro modo per rendere fluenti i setter JavaBean: definire una classe builder interna per ciascun JavaBean. Se stai usando strumenti che vengono confusi dai setter che restituiscono valori, il modello di Paulo potrebbe essere d'aiuto.



3

Sono a favore di setter con ritorni "questo". Non mi importa se non è conforme ai fagioli. Per me, se va bene avere l'espressione "=" / istruzione, allora i setter che restituiscono valori vanno bene.


2

Preferivo questo approccio, ma ho deciso di non farlo.

Motivi:

  • Leggibilità. Rende il codice più leggibile per avere ogni setFoo () su una riga separata. Di solito leggi il codice molte, molte più volte rispetto alla singola volta che lo scrivi.
  • Effetto collaterale: setFoo () dovrebbe solo impostare il campo foo, nient'altro. Restituire questo è un extra "CHE COSA era quello".

Il modello Builder che ho visto non usa la convenzione setFoo (foo) .setBar (bar) ma più foo (foo) .bar (bar). Forse proprio per questi motivi.

È, come sempre una questione di gusti. Mi piace solo l'approccio delle "meno sorprese".


2
Sono d'accordo sull'effetto collaterale. I setter che restituiscono cose violano il loro nome. Stai impostando foo, ma ottieni un oggetto indietro? È un nuovo oggetto o ho modificato il vecchio?
Crunchdog,

2

Questo particolare modello si chiama Method Chaining. Link Wikipedia , questo ha più spiegazioni ed esempi di come è fatto in vari linguaggi di programmazione.

PS: Ho pensato di lasciarlo qui, dato che stavo cercando il nome specifico.


1

A prima vista: "Spaventoso!".

A pensarci ancora

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

è in realtà meno soggetto a errori di

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

Così abbastanza interessante. Aggiunta di idea alla borsa degli attrezzi ...


1
No, è ancora orribile. Dal punto di vista della manutenzione, quest'ultimo è migliore perché è più facile da leggere. Inoltre, i controllori di codice automatizzati come CheckStyle impongono le linee di 80 caratteri per impostazione predefinita: il codice verrebbe comunque avvolto, aggravando il problema di leggibilità / manutenzione. E infine - è Java; non c'è alcun vantaggio nel scrivere tutto su una sola riga quando verrà comunque compilato in codice byte.
OMG Pony il

1
personalmente, penso che il primo sia più facile da leggere, specialmente se stai creando diversi oggetti in questo modo.
Ken Liu,

@Ken: codifica un metodo. Scrivi una copia in formato fluente; un'altra copia nell'altra. Ora dai le due copie a un paio di persone e chiedi quale trovano più facile da leggere. Più veloce da leggere, più veloce da codificare.
OMG Pony il

Come la maggior parte degli strumenti, può essere facile da usare eccessivamente. JQuery è orientato attorno a questa tecnica ed è quindi incline a lunghe catene di chiamate che ho trovato compromette la leggibilità.
staticsan,

2
Sarebbe bene se ci fossero interruzioni di riga prima di ogni punto e rientri. Quindi, sarebbe leggibile come la seconda versione. In realtà di più, perché all'inizio non c'è ridondante list.
MauganRa,

1

Sì, penso sia una buona idea.

Se potessi aggiungere qualcosa, che dire di questo problema:

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

Questo funzionerà:

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

Questo non sarà accettato da Eclipse! :

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

Questo perché setName () restituisce un People e non un Friend, e non esiste PeoplesetNickName.

Come potremmo scrivere setter per restituire la classe SELF invece del nome della classe?

Qualcosa del genere andrebbe bene (se esistesse la parola chiave SELF). Questo esiste comunque?

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

1
Ci sono alcuni altri compilatori Java oltre a Eclipse che non lo accettano :). Fondamentalmente, stai eseguendo uccelli del sistema di tipi Java (che è statico, non dinamico come alcuni linguaggi di scripting): dovrai lanciare ciò che esce da setName () su un amico prima di poter impostareNickName () su di esso. Quindi, sfortunatamente, per le gerarchie ereditarie questo elimina gran parte del vantaggio della leggibilità e rende questa tecnica altrimenti potenzialmente utile non così utile.
Cornel Masson,

5
Usa generici. class Chainable <Self extends Chainable> {public Self doSomething () {return (Self) this;}} Non è tecnicamente sicuro per i tipi (eseguirà il cast di classe se implementi la classe con un tipo Self a cui non puoi fare caso), ma è una grammatica corretta e le sottoclassi restituiranno il proprio tipo.
Ajax

Inoltre: questo "SÉ" che Baptiste sta usando è chiamato un tipo di sé, non presente in molte lingue (Scala è davvero l'unico a cui riesco a pensare in questo momento), dove come Ajax usa Generics è il cosiddetto "Curiosamente modello di modello ricorrente ", che tenta di far fronte a una mancanza del tipo di sé, ma presenta alcuni svantaggi: (da class C<S extends C<S>>, che è più sicuro di un piano S extends C. a) Se A extends B, e B extends C<B>e hai una A, sai solo che un B viene restituito a meno che A non sostituisca ogni singolo metodo. b) Non puoi indicare un locale, non grezzo C<C<C<C<C<...>>>>>.
Adowrath,

1

In generale è una buona pratica, ma potrebbe essere necessario che le funzioni di tipo set utilizzino il tipo booleano per determinare se l'operazione è stata completata correttamente o meno, anche in questo caso. In generale, non esiste un dogma per dire che questo è buono o letto, viene dalla situazione, ovviamente.


2
Che ne dici di usare le eccezioni per indicare la condizione di errore? I codici di errore possono essere facilmente ignorati come molti programmatori C hanno dolorosamente appreso. Le eccezioni possono riempire la pila fino al punto in cui possono essere gestite.
ddimitrov,

Le eccezioni sono preferibili di solito, ma i codici di errore sono utili anche quando non è possibile utilizzare le eccezioni.
Narek,

1
Ciao @Narek, forse potresti elaborare in quali casi è preferibile utilizzare codici di errore nei setter, piuttosto che eccezioni e perché?
ddimitrov,

0

Dalla dichiarazione

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

sto vedendo due cose

1) Dichiarazione insignificante. 2) Mancanza di leggibilità.


0

Questo potrebbe essere meno leggibile

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

o questo

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

Questo è molto più leggibile di:

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

8
Penso che sia abbastanza leggibile se non provi a mettere tutto il tuo codice su una riga.
Ken Liu,

0

Sto realizzando i miei setter da un po 'di tempo e l'unico vero problema è con le librerie che si attengono ai rigidi getPropertyDescriptors per ottenere i bean bean reader / writer. In questi casi, il tuo "bean" java non avrà gli scrittori che ti aspetteresti.

Ad esempio, non l'ho testato di sicuro, ma non sarei sorpreso che Jackson non li riconosca come setter durante la creazione di oggetti java da json / maps. Spero di sbagliarmi su questo (lo proverò presto).

In effetti, sto sviluppando un ORM leggero incentrato su SQL e devo aggiungere un po 'di codice tra getPropertyDescriptors a setter riconosciuti che restituiscono questo.


0

Molto tempo fa risposta, ma i miei due centesimi ... Va bene. Vorrei che questa interfaccia fluida fosse usata più spesso.

Ripetendo la variabile 'factory' non si aggiungono ulteriori informazioni di seguito:

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

Questo è più pulito, imho:

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

Naturalmente, come una delle risposte già menzionate, l'API Java dovrebbe essere ottimizzata per farlo correttamente in alcune situazioni, come eredità e strumenti.


0

È meglio usare costrutti di altre lingue se disponibili. Per esempio, in Kotlin, si usa con , si applicano , o lasciare . Se si utilizza questo approccio, non sarà necessario restituire un'istanza dal proprio setter.

Questo approccio consente al codice client di essere:

  • Indifferente al tipo restituito
  • Più facile da mantenere
  • Evita gli effetti collaterali del compilatore

Ecco alcuni esempi.

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

Se sto scrivendo un'API, utilizzo "return this" per impostare valori che verranno impostati solo una volta. Se ho altri valori che l'utente dovrebbe essere in grado di modificare, utilizzo invece un setter vuoto standard.

Tuttavia, è davvero una questione di preferenza e concatenare i setter sembra piuttosto bello, secondo me.


0

Sono d'accordo con tutti i poster che affermano che ciò rompe le specifiche JavaBeans. Ci sono ragioni per preservarlo, ma sento anche che l'uso di questo Builder Pattern (a cui è stato accennato) ha il suo posto; purché non sia usato ovunque, dovrebbe essere accettabile. "It's Place", per me, è dove il punto finale è una chiamata a un metodo "build ()".

Esistono ovviamente altri modi per impostare tutte queste cose, ma il vantaggio qui è che evita 1) costruttori pubblici a molti parametri e 2) oggetti parzialmente specificati. Qui, hai il builder di raccogliere ciò che è necessario e quindi chiamare il suo "build ()" alla fine, che può quindi garantire che un oggetto parzialmente specificato non sia costruito, poiché a quell'operazione può essere data una visibilità non pubblica. L'alternativa sarebbe "oggetti parametro", ma che IMHO riporta il problema indietro di un livello.

Non mi piacciono i costruttori di molti parametri perché rendono più probabile il passaggio di molti argomenti dello stesso tipo, il che può semplificare il passaggio di argomenti errati ai parametri. Non mi piace usare molti setter perché l'oggetto potrebbe essere usato prima che sia completamente configurato. Inoltre, l'idea di avere valori predefiniti basati su scelte precedenti è meglio servita con un metodo "build ()".

In breve, penso che sia una buona pratica, se usato correttamente.


-4

Cattiva abitudine: un setter ha ottenuto un risultato migliore

che dire di dichiarare esplicitamente un metodo, che lo fa per U

setPropertyFromParams(array $hashParamList) { ... }

non va bene per il completamento automatico, il refactoring e la leggibilità e il codice del boilerplate
Andreas Dietrich

Perché: 1) Devi ricordare l'ordine o leggerlo dai documenti, 2) perdi tutta la sicurezza del tipo in fase di compilazione, 3) devi lanciare i valori (questo e 2) è ovviamente un problema in una dinamica lingua, naturalmente), 4) è possibile non estendere questo utilmente diverso controllare la matrice di lunghezza ad ogni chiamata, e 5) se si cambia qualcosa, sia esso ordinare o addirittura l'esistenza di alcuni valori, è possibile non accertare l'assenza di runtime né compilare timewithout dubbio a tutti . Con setter: 1), 2), 3): Nessun problema. 4) L'aggiunta di nuovi metodi non rompe nulla. 5) messaggio di errore esplicito.
Adowrath,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.