Ist es eine schlechte Praxis, einen Setter dazu zu bringen, „dies“ zurückzugeben?

249

Ist es eine gute oder schlechte Idee, Setter in Java dazu zu bringen, "dies" zurückzugeben?

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

Dieses Muster kann nützlich sein, da Sie dann Setter wie folgt verketten können:

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

an Stelle von:

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

... aber es widerspricht irgendwie der Standardkonvention. Ich nehme an, es könnte sich lohnen, nur weil es diesen Setter dazu bringen kann, etwas anderes Nützliches zu tun. Ich habe gesehen, dass dieses Muster an einigen Stellen verwendet wurde (z. B. JMock, JPA), aber es scheint ungewöhnlich und wird nur allgemein für sehr gut definierte APIs verwendet, bei denen dieses Muster überall verwendet wird.

Aktualisieren:

Was ich beschrieben habe, ist offensichtlich gültig, aber was ich wirklich suche, sind einige Gedanken darüber, ob dies allgemein akzeptabel ist und ob es Fallstricke oder verwandte Best Practices gibt. Ich kenne das Builder-Muster, aber es ist etwas komplizierter als das, was ich beschreibe - wie Josh Bloch es beschreibt, gibt es eine zugehörige statische Builder-Klasse für die Objekterstellung.

Ken Liu
quelle
1
Da ich dieses Entwurfsmuster vor einiger Zeit gesehen habe, mache ich dies, wo immer es möglich ist. Wenn eine Methode nicht explizit etwas zurückgeben muss, um ihre Arbeit zu erledigen, wird sie jetzt zurückgegeben this. Manchmal ändere ich die Funktion sogar so, dass sie, anstatt einen Wert zurückzugeben, auf ein Mitglied des Objekts angewendet wird, nur damit ich dies tun kann. Es ist wundervoll. :)
Inversus
4
Für Teleskop-Setter, die selbst und in Buildern zurückkehren, bevorzuge ich die Verwendung von withName (String name) anstelle von setName (String name). Wie Sie bereits betont haben, besteht eine übliche Praxis und Erwartung für den Setter darin, nichtig zurückzukehren. "Nicht-Standard" -Setter verhalten sich möglicherweise nicht gut mit vorhandenen Frameworks, z. B. JPA-Entitätsmanagern, Spring usw.
14.
Bitte führen Sie vor jedem Aufruf Zeilenumbrüche ein :) Und konfigurieren Sie Ihre IDE oder besorgen Sie sich eine richtige, wenn dies nicht beachtet wird.
MauganRa
Weit verbreitete Frameworks (z. B. Spring und Hibernate) halten sich strikt (zumindest früher) an die Konvention für
Legna,

Antworten:

83

Ich glaube nicht, dass irgendetwas speziell daran falsch ist, es ist nur eine Frage des Stils. Es ist nützlich, wenn:

  • Sie müssen viele Felder gleichzeitig festlegen (auch beim Bau).
  • Sie wissen, welche Felder Sie zum Zeitpunkt des Schreibens des Codes festlegen müssen, und
  • Es gibt viele verschiedene Kombinationen, für die Sie Felder festlegen möchten.

Alternativen zu dieser Methode könnten sein:

  1. Ein Mega-Konstruktor (Nachteil: Möglicherweise übergeben Sie viele Nullen oder Standardwerte, und es ist schwierig zu wissen, welcher Wert was entspricht.)
  2. Mehrere überladene Konstruktoren (Nachteil: wird unhandlich, wenn Sie mehr als ein paar haben)
  3. Factory / statische Methoden (Nachteil: wie überladene Konstruktoren - wird unhandlich, sobald es mehr als ein paar gibt)

Wenn Sie nur einige Eigenschaften gleichzeitig festlegen, würde ich sagen, dass es sich nicht lohnt, "dies" zurückzugeben. Es fällt auf jeden Fall herunter, wenn Sie sich später dazu entschließen, etwas anderes zurückzugeben, z. B. einen Status- / Erfolgsindikator / eine Nachricht.

Tom Clift
quelle
1
Nun, im Allgemeinen gibt man laut Konvention sowieso nichts von einem Setter zurück.
Ken Liu
17
Vielleicht nicht, aber ein Setter behält nicht unbedingt seinen ursprünglichen Zweck. Was früher eine Variable war, kann sich in einen Zustand ändern, der mehrere Variablen umfasst oder andere Nebenwirkungen hat. Einige Setter geben möglicherweise einen vorherigen Wert zurück, andere geben möglicherweise einen Fehlerindikator zurück, wenn ein Fehler für eine Ausnahme zu häufig ist. Dies wirft jedoch einen weiteren interessanten Punkt auf: Was ist, wenn ein von Ihnen verwendetes Tool / Framework Ihre Setter nicht erkennt, wenn sie Rückgabewerte haben?
Tom Clift
12
@ Tom guter Punkt, dies bricht die "Java Bean" -Konvention für Getter und Setter.
Andy White
2
@ TomClift Verursacht das Brechen der "Java Bean" -Konvention Probleme? Sind Bibliotheken, die die "Java Bean" -Konvention verwenden und den Rückgabetyp oder nur die Methodenparameter und den Methodennamen betrachten.
Theo Briscoe
3
Aus diesem Grund gibt es das Builder-Muster. Setter sollten nicht etwas zurückgeben, sondern einen Builder erstellen, wenn der benötigte besser aussieht und weniger Code benötigt :)
RicardoE
106

Es ist keine schlechte Praxis. Es ist eine zunehmend übliche Praxis. In den meisten Sprachen müssen Sie sich nicht mit dem zurückgegebenen Objekt befassen, wenn Sie dies nicht möchten. Dadurch wird die Syntax für die "normale" Setter-Verwendung nicht geändert, sondern Sie können Setter miteinander verketten.

Dies wird üblicherweise als Builder-Muster oder fließende Schnittstelle bezeichnet .

Es ist auch in der Java-API üblich:

String s = new StringBuilder().append("testing ").append(1)
  .append(" 2 ").append(3).toString();
Cletus
quelle
26
Es wird oft verwendet , in Bauer, aber ich würde nicht sagen , „Das ist ... ein Builder - Muster genannt“.
Laurence Gonsalves
10
Es ist lustig für mich, dass einige der Gründe für fließende Schnittstellen darin bestehen, dass sie das Lesen von Code erleichtern. Ich konnte sehen, dass es bequemer ist zu schreiben, aber es scheint mir, dass es schwieriger zu lesen ist. Das ist die einzige wirkliche Meinungsverschiedenheit, die ich damit habe.
Brent schreibt Code
30
Es ist auch als Zugwrack-Antimuster bekannt. Das Problem ist, dass Sie keine Ahnung haben, welcher Aufruf null zurückgegeben hat, wenn ein Nullzeiger-Exception-Stack-Trace eine Zeile wie diese enthält. Das heißt nicht, dass Verkettungen um jeden Preis vermieden werden sollten, aber hüten Sie sich vor schlechten Bibliotheken (insbesondere hausgemachten).
ddimitrov
18
@ddimitrov aslong , wie Sie es begrenzen auf die Rückkehr dieses wäre es nie ein Problem sein (nur die erste Invokation könnte NPE werfen)
Stefan
4
Es ist einfacher zu schreiben UND zu lesen, vorausgesetzt, Sie setzen Zeilenumbrüche und Einrückungen dort, wo die Lesbarkeit sonst leiden würde! (weil es redundantes Durcheinander von sich wiederholendem Code wie bla.foo.setBar1(...) ; bla.foo.setBar2(...)beim Schreiben vermeidet bla.foo /* newline indented */.setBar1(...) /* underneath previous setter */ .setBar2(...)(Zeilenumbrüche können in einem SO-Kommentar wie diesem nicht verwendet werden :-( ... ich hoffe, Sie bekommen den Punkt, wenn Sie 10 solcher Setter oder komplexere Aufrufe in Betracht ziehen)
Andreas Dietrich
90

Zusammenfassen:

  • Es wird als "fließende Schnittstelle" oder "Methodenverkettung" bezeichnet.
  • Dies ist kein "Standard" -Java, obwohl Sie es heutzutage mehr und mehr sehen (funktioniert hervorragend in jQuery).
  • Es verstößt gegen die JavaBean-Spezifikation und kann daher mit verschiedenen Tools und Bibliotheken, insbesondere JSP-Buildern und Spring, in Konflikt geraten.
  • Dies kann einige Optimierungen verhindern, die die JVM normalerweise durchführen würde
  • Einige Leute denken, es bereinigt den Code, andere denken, es sei "grässlich"

Ein paar andere Punkte nicht erwähnt:

  • Dies verstößt gegen das Prinzip, dass jede Funktion eine (und nur eine) Sache tun sollte. Sie können oder können nicht daran glauben, aber in Java glaube ich, dass es gut funktioniert.

  • IDEs generieren diese nicht für Sie (standardmäßig).

  • Ich endlich, hier ist ein realer Datenpunkt. Ich hatte Probleme mit einer solchen Bibliothek. Der Abfrage-Generator von Hibernate ist ein Beispiel dafür in einer vorhandenen Bibliothek. Da die set * -Methoden von Query Abfragen zurückgeben, ist es unmöglich, anhand der Signatur zu erkennen, wie sie verwendet werden sollen. Beispielsweise:

    Query setWhatever(String what);
  • Dies führt zu einer Mehrdeutigkeit: Ändert die Methode das aktuelle Objekt (Ihr Muster) oder ist Query möglicherweise wirklich unveränderlich (ein sehr beliebtes und wertvolles Muster), und die Methode gibt ein neues zurück. Dies erschwert lediglich die Verwendung der Bibliothek, und viele Programmierer nutzen diese Funktion nicht. Wenn Setter Setter wären, wäre es klarer, wie man sie benutzt.

ndp
quelle
5
Übrigens ist es "fließend", nicht "fließend" ... da Sie damit eine Reihe von Methodenaufrufen wie eine gesprochene Sprache strukturieren können.
Ken Liu
1
Der letzte Punkt zur Unveränderlichkeit ist sehr wichtig. Das einfachste Beispiel ist String. Java-Entwickler erwarten, dass sie bei Verwendung von Methoden für String eine völlig neue Instanz erhalten und nicht dieselbe, sondern eine geänderte Instanz. Bei einer fließenden Schnittstelle müsste in der Methodendokumentation erwähnt werden, dass das zurückgegebene Objekt 'this' anstelle einer neuen Instanz ist.
MeTTeO
7
Obwohl ich insgesamt zustimme, stimme ich nicht zu, dass dies gegen das Prinzip "nur eins tun" verstößt. Die Rückkehr thisist kaum eine komplexe Raketenwissenschaft. :-)
user949300
Zusätzlicher Punkt: Es verstößt auch gegen das Prinzip der Befehl-Abfrage-Trennung.
Marton_hun
84

Ich bevorzuge die Verwendung von 'with'-Methoden dafür:

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

So:

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

Warnung : Diese withXSyntax wird häufig verwendet, um "Setter" für unveränderliche Objekte bereitzustellen. Daher können Aufrufer dieser Methoden vernünftigerweise erwarten, dass sie neue Objekte erstellen, anstatt die vorhandene Instanz zu mutieren. Vielleicht wäre eine vernünftigere Formulierung so etwas wie:

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

Mit der Namenskonvention chainetXyz () sollte praktisch jeder zufrieden sein.

qualidafial
quelle
18
+1 Für interessante Konventionen. Ich werde es nicht in meinen eigenen Code übernehmen, da es so aussieht, als müssten Sie jetzt für jedes Klassenfeld ein get, ein setund ein haben with. Es ist jedoch immer noch eine interessante Lösung. :)
Paul Manta
1
Es hängt davon ab, wie oft Sie die Setter anrufen. Ich habe festgestellt, dass es die zusätzliche Mühe wert ist, sie hinzuzufügen, wenn diese Setter häufig aufgerufen werden, da dies den Code überall vereinfacht. YMMV
qualidafial
2
Und wenn Sie dies zu Project Lombokden @Getter/@SetterAnmerkungen hinzufügen würden ... wäre das fantastisch für die Verkettung. Oder Sie könnten etwas wie den KestrelKombinator verwenden ( github.com/raganwald/Katy) ) JQuery- und Javascript-Freunde verwenden.
Ehtesh Choudhury
4
@ AlikElzin-kilaka Eigentlich habe ich gerade bemerkt, dass die unveränderlichen Klassen java.time in Java 8 dieses Muster verwenden, z. B. LocalDate.withMonth, withYear usw.
qualidafial
4
Das withPräfix ist eine andere Konvention. als @qualidafial gab ein Beispiel. Methoden mit dem Präfix withsollten nicht zurückgeben this, sondern eine neue Instanz wie die aktuelle Instanz, aber withdiese Änderung. Dies geschieht, wenn Ihre Objekte unveränderlich sein sollen. Wenn ich also eine Methode sehe, der ein Präfix vorangestellt ist with, gehe ich davon aus, dass ich ein neues Objekt erhalte, nicht dasselbe Objekt.
Tempcke
26

Wenn Sie nicht 'this'vom Setter zurückkehren möchten, aber nicht die zweite Option verwenden möchten, können Sie die folgende Syntax verwenden, um Eigenschaften festzulegen:

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

Abgesehen davon denke ich, dass es in C # etwas sauberer ist:

list.Add(new Employee() {
    Name = "Jack Sparrow",
    Id = 1,
    Foo = "bacon!"
});
Luke Quinane
quelle
16
Die Initialisierung mit doppelter Klammer kann Probleme mit Gleichheit haben, da dadurch eine anonyme innere Klasse erstellt wird. siehe c2.com/cgi/wiki?DoubleBraceInitialization
Csaba_H
@Csaba_H Dieses Problem ist eindeutig die Schuld der Person, die die equalsMethode verpfuscht hat . Es gibt sehr saubere Möglichkeiten, mit anonymen Klassen umzugehen, equalswenn Sie wissen, was Sie tun.
AJMansfield
eine neue (anonyme) Klasse dafür erstellen? für jeden Fall?
user85421
11

Es bricht nicht nur die Konvention von Gettern / Setzern, sondern auch das Referenzframework für Java 8-Methoden. MyClass::setMyValueist ein BiConsumer<MyClass,MyValue>und myInstance::setMyValueist ein Consumer<MyValue>. Wenn Ihr Setter zurückgegeben wird this, handelt es sich nicht mehr um eine gültige Instanz von Consumer<MyValue>, sondern um eine Function<MyValue,MyClass>und führt dazu, dass alles, was Methodenreferenzen auf diese Setter verwendet (vorausgesetzt, es handelt sich um ungültige Methoden), unterbrochen wird.

Steve K.
quelle
2
Es wäre fantastisch, wenn Java eine Möglichkeit hätte, nach Rückgabetyp zu überladen, nicht nur nach der JVM. Sie können viele dieser Änderungen leicht umgehen.
Adowrath
1
Sie können jederzeit eine funktionale Schnittstelle definieren, die sowohl erweitert Consumer<A>als Function<A,B>auch eine Standardimplementierung von bereitstellt void accept(A a) { apply(a); }. Dann kann es leicht als eines von beiden verwendet werden und bricht keinen Code, der ein bestimmtes Formular erfordert.
Steve K
Argh! Das ist einfach falsch! Dies wird als Void-Kompatibilität bezeichnet. Ein Setter, der einen Wert zurückgibt, kann als Verbraucher fungieren. ideone.com/ZIDy2M
Michael
8

Ich kenne Java nicht, aber ich habe dies in C ++ getan. Andere Leute haben gesagt, es macht die Zeilen sehr lang und sehr schwer zu lesen, aber ich habe es oft so gemacht:

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

Das ist noch besser:

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

Zumindest denke ich. Aber Sie können mich gerne herabstimmen und mich einen schrecklichen Programmierer nennen, wenn Sie es wünschen. Und ich weiß nicht, ob Sie dies überhaupt in Java tun dürfen.

Carson Myers
quelle
Das "noch bessere" eignet sich nicht gut für die Format-Quellcode-Funktionalität, die in modernen IDEs verfügbar ist. Unglücklicherweise.
Thorbjørn Ravn Andersen
Sie haben wahrscheinlich Recht ... Der einzige Auto-Formatierer, den ich verwendet habe, ist das automatische Einrücken von Emacs.
Carson Myers
2
Quellcode-Formatierer können nach jedem Methodenaufruf in der Kette mit einem einfachen // erzwungen werden. Es macht Ihren Code ein wenig hässlich, aber nicht so sehr, wenn Ihre vertikale Reihe von Anweisungen horizontal neu formatiert wird.
Qualidafial
@qualidafial Sie benötigen nicht //nach jeder Methode, wenn Sie Ihre IDE so konfigurieren, dass bereits umbrochene Zeilen nicht verknüpft werden (z. B. Eclipse> Eigenschaften> Java> Codestil> Formatierer> Zeilenumbruch> Niemals bereits umbrochene Zeilen verknüpfen).
DJDaveMark
6

Dieses Schema (Wortspiel beabsichtigt), das als "fließende Schnittstelle" bezeichnet wird, wird jetzt ziemlich populär. Es ist akzeptabel, aber es ist nicht wirklich meine Tasse Tee.

Mittags Seide
quelle
6

Da es nicht void zurückgibt, ist es kein gültiger JavaBean-Eigenschaftssetter mehr. Dies kann von Bedeutung sein, wenn Sie einer der sieben Menschen auf der Welt sind, die visuelle "Bean Builder" -Tools verwenden, oder einer der 17, die JSP-bean-setProperty-Elemente verwenden.

Ken
quelle
Es ist auch wichtig, wenn Sie Bean-fähige Frameworks wie Spring verwenden.
ddimitrov
6

Zumindest theoretisch kann dies die Optimierungsmechanismen der JVM beschädigen, indem falsche Abhängigkeiten zwischen Aufrufen festgelegt werden.

Es soll syntaktischer Zucker sein, kann aber tatsächlich Nebenwirkungen in der virtuellen Maschine des superintelligenten Java 43 hervorrufen.

Deshalb stimme ich nein, benutze es nicht.

Marian
quelle
10
Interessant ... könnten Sie das etwas erweitern?
Ken Liu
3
Denken Sie nur daran, wie superskalare Prozessoren mit paralleler Ausführung umgehen. Das Objekt, für das die zweite setMethode ausgeführt werden soll, hängt von der ersten setMethode ab, obwohl es dem Programmierer bekannt ist.
Marian
2
Ich folge immer noch nicht. Wenn Sie Foo und dann Bar mit zwei separaten Anweisungen festlegen, hat das Objekt, für das Sie Bar festlegen, einen anderen Status als das Objekt, für das Sie Foo festlegen. Daher konnte der Compiler diese Anweisungen auch nicht parallelisieren. Zumindest sehe ich nicht, wie es könnte, ohne eine ungerechtfertigte Annahme einzuführen. (Da ich keine Ahnung davon habe, werde ich nicht leugnen, dass Java 43 tatsächlich die Parallelisierung in dem einen Fall durchführt, aber nicht den anderen und einführen die ungerechtfertigte Annahme in dem einen Fall, aber nicht in dem anderen).
Masonk
12
Wenn Sie nicht wissen, testen Sie. -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Das java7 jdk integriert definitiv verkettete Methoden und führt ungefähr die gleiche Anzahl von Iterationen durch, die erforderlich sind, um Leersetzer als heiß zu markieren und sie auch zu inline. Denken Sie, Sie unterschätzen die Leistungsfähigkeit der Opcode-Bereinigungsalgorithmen der JVM. Wenn es weiß, dass Sie dies zurückgeben, überspringt es den Opcode jrs (Java Return Statement) und belässt dies einfach auf dem Stapel.
Ajax
1
Andreas, ich stimme zu, aber Probleme treten auf, wenn Sie Schicht für Schicht ineffizienten Code haben. In 99% der Fälle sollten Sie aus Gründen der Klarheit codieren, was hier häufig gesagt wird. Es gibt aber auch Zeiten, in denen Sie realistisch sein und Ihre jahrelange Erfahrung nutzen müssen, um im allgemeinen architektonischen Sinne vorzeitig zu optimieren.
LegendLength
6

Es ist überhaupt keine schlechte Praxis. Es ist jedoch nicht mit JavaBeans Spec kompatibel .

Und es gibt viele Spezifikationen, die von diesen Standard-Accessoren abhängen.

Sie können sie jederzeit nebeneinander existieren lassen.

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

Jetzt können wir sie zusammen verwenden.

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

Hier kommt eine andere Version für unveränderliche Objekte.

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

Jetzt können wir das tun.

new Some.Builder().value("value").build().getValue();
Jin Kwon
quelle
2
Meine Bearbeitung wurde abgelehnt, aber Ihr Builder-Beispiel ist nicht korrekt. Erstens gibt .value () nichts zurück und legt nicht einmal das someFeld fest. Zweitens sollten Sie einen Schutz hinzufügen und in build () festlegen some, nulldamit er Somewirklich unveränderlich ist. Andernfalls können Sie builder.value()dieselbe Builder-Instanz erneut aufrufen . Und schließlich haben Sie ja einen Builder, aber Sie haben Someimmer noch einen öffentlichen Konstruktor, was bedeutet, dass Sie die Verwendung des Builders nicht offen befürworten, dh der Benutzer weiß nichts anderes davon, als eine Methode zum Festlegen einer benutzerdefinierten Methode auszuprobieren oder nach ihr zu suchen valueüberhaupt.
Adowrath
@Adowrath Wenn die Antwort falsch ist, sollten Sie Ihre eigene Antwort schreiben, nicht versuchen, die eines anderen in Form zu bringen
CalvT
1
@ JinKwon Großartig. Vielen Dank! Und tut mir leid, wenn ich vorher unhöflich wirkte.
Adowrath
1
@Adowrath Bitte zögern Sie nicht für weitere Kommentare für Verbesserungen. Zu Ihrer Information, ich bin nicht derjenige, der Ihre Bearbeitung abgelehnt hat. :)
Jin Kwon
1
Ich weiß, ich weiß. ^^ Und danke für die neue Version, die jetzt einen wirklich veränderlichen "Immutable-Some" Builder bietet. Und es ist eine cleverere Lösung als meine Bearbeitungsversuche, die den Code im Vergleich überladen haben.
Adowrath
4

Wenn Sie dieselbe Konvention in der gesamten Anwendung verwenden, scheint dies in Ordnung zu sein.

Wenn andererseits ein vorhandener Teil Ihrer Anwendung die Standardkonvention verwendet, würde ich mich daran halten und Builder zu komplizierteren Klassen hinzufügen

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;
    }
}
Marcin Szymczak
quelle
1
Genau dafür wurde das Builder-Muster entwickelt. Es bricht keine Lambdas in Java 8, es bricht keine kniffligen JavaBeans-Tools und es verursacht keine Optimierungsprobleme in JVM (da das Objekt nur während der Instanziierung existiert). Es löst auch das Problem "viel zu viele Konstruktoren", das Sie erhalten, wenn Sie einfach keine Builder verwenden, und beseitigt die Heap-Verschmutzung durch anonyme Klassen mit doppelter Klammer.
ndm13
Interessanter Punkt - Ich stelle fest, dass Sie Builder wahrscheinlich ganz vermeiden können, wenn fast nichts in Ihrer Klasse unveränderlich ist (z. B. eine hoch konfigurierbare GUI).
Philip Guin
4

Paulo Abrantes bietet eine andere Möglichkeit, JavaBean-Setter fließend zu machen: Definieren Sie für jede JavaBean eine innere Builder-Klasse. Wenn Sie Tools verwenden, die von Setzern, die Werte zurückgeben, durcheinander gebracht werden, kann das Muster von Paulo hilfreich sein.

Jim Ferrans
quelle
2
@ cdunn2001, verwenden Wayback-Maschine, Luke
Msangel
3

Ich bin für Setter, die "dies" zurückgeben. Es ist mir egal, ob es nicht Bohnen-konform ist. Wenn es in Ordnung ist, den Ausdruck / die Anweisung "=" zu haben, sind Setter, die Werte zurückgeben, in Ordnung.

Monty Hall
quelle
2

Früher habe ich diesen Ansatz bevorzugt, aber ich habe mich dagegen entschieden.

Gründe dafür:

  • Lesbarkeit. Dadurch wird der Code besser lesbar, wenn jedes setFoo () in einer separaten Zeile steht. Normalerweise lesen Sie den Code viel, viel öfter als beim einzelnen Schreiben.
  • Nebeneffekt: setFoo () sollte nur das Feld foo setzen, sonst nichts. Dies zurückzugeben ist ein zusätzliches "WAS war das".

Das Builder-Muster, das ich gesehen habe, verwendet nicht die setFoo (foo) .setBar (bar) -Konvention, sondern mehr foo (foo) .bar (bar). Vielleicht aus genau diesen Gründen.

Es ist wie immer Geschmackssache. Ich mag nur den Ansatz der "geringsten Überraschungen".

Thorbjørn Ravn Andersen
quelle
2
Ich stimme der Nebenwirkung zu. Setter, die Sachen zurückgeben, verletzen ihren Namen. Sie setzen foo, bekommen aber ein Objekt zurück? Ist das ein neues Objekt oder habe ich das alte geändert?
Crunchdog
2

Dieses spezielle Muster wird als Methodenverkettung bezeichnet. Wikipedia-Link , hier finden Sie weitere Erklärungen und Beispiele dafür, wie es in verschiedenen Programmiersprachen gemacht wird.

PS: Ich habe gerade daran gedacht, es hier zu lassen, da ich nach dem spezifischen Namen gesucht habe.

capt.swag
quelle
1

Auf den ersten Blick: "Grässlich!".

Bei weiterem Nachdenken

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

ist eigentlich weniger fehleranfällig als

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

Also ziemlich interessant. Idee zur Werkzeugtasche hinzufügen ...

djna
quelle
1
Nein, es ist immer noch schrecklich. Letzteres ist aus Wartungssicht besser, weil es leichter zu lesen ist. Darüber hinaus erzwingen automatisierte Codeprüfer wie CheckStyle, dass Zeilen standardmäßig 80 Zeichen lang sind. Der Code wird ohnehin umbrochen, was das Problem der Lesbarkeit / Wartung noch verschärft. Und schließlich - es ist Java; Es hat keinen Vorteil, alles in eine einzelne Zeile zu schreiben, wenn es sowieso zu Bytecode kompiliert wird.
OMG Ponys
1
Ich persönlich denke, Ersteres ist leichter zu lesen, insbesondere wenn Sie auf diese Weise mehrere Objekte erstellen.
Ken Liu
@ Ken: Codiere eine Methode. Schreiben Sie eine Kopie in fließendem Format. eine andere Kopie in der anderen. Geben Sie nun die beiden Kopien an einige Personen weiter und fragen Sie, welche für sie leichter zu lesen sind. Schneller zu lesen, schneller zu codieren.
OMG Ponys
Wie die meisten Werkzeuge kann es leicht zu einer Überbeanspruchung kommen. JQuery orientiert sich an dieser Technik und ist daher anfällig für lange Anrufketten, von denen ich festgestellt habe, dass sie die Lesbarkeit beeinträchtigen.
Statik
2
Es wäre in Ordnung, wenn vor jedem Punkt Zeilenumbrüche und Einrückungen auftreten würden. Dann wäre es genauso lesbar wie die zweite Version. Eigentlich mehr, weil es listam Anfang keine Redundanz gibt .
MauganRa
1

Ja, ich denke es ist eine gute Idee.

Wenn ich etwas hinzufügen könnte, was ist mit diesem Problem:

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

Das wird funktionieren :

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

Dies wird von Eclipse nicht akzeptiert! ::

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

Dies liegt daran, dass setName () ein Volk und keinen Freund zurückgibt und es keinen PeoplesetNickName gibt.

Wie können wir Setter schreiben, um die SELF-Klasse anstelle des Klassennamens zurückzugeben?

So etwas wäre in Ordnung (wenn das Schlüsselwort SELF existieren würde). Existiert das überhaupt?

class People
{
    private String name;
    public SELF setName(String name)
    {
        this.name = name;
        return this;
    }
}
Baptiste
quelle
1
Neben Eclipse gibt es noch einige andere Java-Compiler, die das nicht akzeptieren :). Grundsätzlich führen Sie Geflügel des Java-Typsystems aus (das statisch und nicht dynamisch ist wie einige Skriptsprachen): Sie müssen das, was aus setName () kommt, an einen Freund übertragen, bevor Sie setNickName () darauf setzen können. Leider beseitigt dies für Vererbungshierarchien einen Großteil des Lesbarkeitsvorteils und macht diese ansonsten potenziell nützliche Technik nicht so nützlich.
Cornel Masson
5
Verwenden Sie Generika. class Chainable <Self erweitert Chainable> {public Self doSomething () {return (Self) this;}} Es ist technisch nicht typsicher (es wird eine Klassenumwandlung durchführen, wenn Sie die Klasse mit einem Self-Typ implementieren, für den Sie keinen Fall haben können), aber es ist die richtige Grammatik, und Unterklassen geben ihren eigenen Typ zurück.
Ajax
Außerdem: Dieses "SELBST", das Baptiste verwendet, wird als Selbsttyp bezeichnet, der in vielen Sprachen nicht vorhanden ist (Scala ist wirklich die einzige, an die ich derzeit denken kann), wobei Ajax 'Verwendung von Generika das sogenannte "Curiously" ist wiederkehrendes Vorlagenmuster ", das versucht, mit einem Mangel an Selbsttyp fertig zu werden, aber einige Nachteile hat: (von class C<S extends C<S>>, was sicherer ist als eine Ebene S extends C. a) Wenn A extends B, und B extends C<B>und Sie ein A haben, wissen Sie nur, dass a B wird zurückgegeben, es sei denn, A überschreibt jede einzelne Methode. b) Sie können kein lokales, nicht rohes bezeichnen C<C<C<C<C<...>>>>>.
Adowrath
1

Im Allgemeinen ist dies eine gute Vorgehensweise, aber Sie müssen möglicherweise für Funktionen vom Typ set den Booleschen Typ verwenden, um festzustellen, ob der Vorgang erfolgreich abgeschlossen wurde oder nicht. Dies ist auch eine Möglichkeit. Im Allgemeinen gibt es kein Dogma zu sagen, dass dies gut oder Bett ist, es kommt natürlich von der Situation.

Narek
quelle
2
Wie wäre es mit Ausnahmen, um den Fehlerzustand anzuzeigen? Fehlercodes können leicht ignoriert werden, wie viele C-Programmierer schmerzhaft gelernt haben. Ausnahmen können den Stapel bis zu dem Punkt sprudeln lassen, an dem sie behandelt werden können.
ddimitrov
Ausnahmen sind normalerweise vorzuziehen, aber Fehlercodes sind auch nützlich, wenn Sie keine Ausnahmen verwenden können.
Narek
1
Hallo @Narek, vielleicht könnten Sie näher erläutern, in welchen Fällen es vorzuziehen ist, Fehlercodes in Setzern anstelle von Ausnahmen zu verwenden, und warum?
ddimitrov
0

Aus der Aussage

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

Ich sehe zwei Dinge

1) Bedeutungslose Aussage. 2) Unleserlichkeit.

Madhu
quelle
0

Dies ist möglicherweise weniger lesbar

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

oder dieses

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

Dies ist viel besser lesbar als:

Employee employee = new Employee();
employee.setName("Jack Sparrow")
employee.setId(1)
employee.setFoo("bacon!")); 
list.add(employee); 
Scott
quelle
8
Ich denke, es ist ziemlich lesbar, wenn Sie nicht versuchen, Ihren gesamten Code in eine Zeile zu setzen.
Ken Liu
0

Ich mache meine Setter schon eine ganze Weile und das einzige wirkliche Problem sind Bibliotheken, die sich an die strengen getPropertyDescriptors halten, um die Bean-Reaktoren für Bean-Reader / Writer zu erhalten. In diesen Fällen verfügt Ihre Java-Bean nicht über die erwarteten Writer.

Zum Beispiel habe ich es nicht sicher getestet, aber ich wäre nicht überrascht, dass Jackson diese beim Erstellen von Java-Objekten aus json / maps nicht als Setter erkennt. Ich hoffe, ich irre mich in diesem Punkt (ich werde es bald testen).

Tatsächlich entwickle ich ein leichtes SQL-zentriertes ORM und muss erkannten Setzern, die dies zurückgeben, Code über getPropertyDescriptors hinzufügen.

Jeremy Chone
quelle
0

Vor langer Zeit antworten, aber meine zwei Cent ... Es ist in Ordnung. Ich wünschte, diese fließende Schnittstelle würde öfter verwendet.

Durch Wiederholen der Variablen 'factory' werden unten keine weiteren Informationen hinzugefügt:

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

Das ist sauberer, imho:

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

Natürlich müsste, wie eine der bereits erwähnten Antworten, die Java-API optimiert werden, um dies in bestimmten Situationen wie Vererbung und Tools korrekt durchzuführen.

Josef.B
quelle
0

Es ist besser, andere Sprachkonstrukte zu verwenden, falls verfügbar. In Kotlin würden Sie beispielsweise mit verwenden , anwenden oder lassen . Wenn Sie diesen Ansatz, werden Sie nicht wirklich brauchen eine Instanz von Ihrem Setter zurückzukehren.

Dieser Ansatz ermöglicht es Ihrem Client-Code:

  • Gleichgültig gegenüber dem Rückgabetyp
  • Einfacher zu warten
  • Vermeiden Sie Compiler-Nebenwirkungen

Hier sind einige Beispiele.

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"
}
Steven Spungin
quelle
0

Wenn ich eine API schreibe, verwende ich "return this", um Werte festzulegen, die nur einmal festgelegt werden. Wenn ich andere Werte habe, die der Benutzer ändern kann, verwende ich stattdessen einen Standard-Leersetzer.

Es ist jedoch wirklich eine Frage der Präferenz und Verkettungssetter sehen meiner Meinung nach ziemlich cool aus.

Kaiser Keister
quelle
0

Ich bin mit allen Postern einverstanden, die behaupten, dies verstoße gegen die JavaBeans-Spezifikation. Es gibt Gründe, dies beizubehalten, aber ich bin auch der Meinung, dass die Verwendung dieses Builder-Musters (auf das angespielt wurde) seinen Platz hat. Solange es nicht überall verwendet wird, sollte es akzeptabel sein. "It's Place" ist für mich der Endpunkt eines Aufrufs einer "build ()" - Methode.

Es gibt natürlich auch andere Möglichkeiten, all diese Dinge festzulegen, aber der Vorteil hierbei ist, dass 1) öffentliche Konstruktoren mit vielen Parametern und 2) teilweise spezifizierte Objekte vermieden werden. Hier muss der Builder erfassen, was benötigt wird, und am Ende "build ()" aufrufen, um sicherzustellen, dass kein teilweise angegebenes Objekt erstellt wird, da dieser Vorgang weniger als öffentlich sichtbar sein kann. Die Alternative wären "Parameterobjekte", aber das IMHO schiebt das Problem nur um eine Ebene zurück.

Ich mag Konstruktoren mit vielen Parametern nicht, weil sie es wahrscheinlicher machen, dass viele Argumente des gleichen Typs übergeben werden, was es einfacher machen kann, die falschen Argumente an Parameter zu übergeben. Ich mag es nicht, viele Setter zu verwenden, da das Objekt verwendet werden könnte, bevor es vollständig konfiguriert ist. Darüber hinaus wird der Gedanke, Standardwerte basierend auf vorherigen Auswahlmöglichkeiten zu haben, mit einer "build ()" - Methode besser bedient.

Kurz gesagt, ich denke, es ist eine gute Praxis, wenn es richtig angewendet wird.

Javaneer
quelle
-4

Schlechte Angewohnheit: Ein Setter hat einen Getter bekommen

Was ist mit der expliziten Deklaration einer Methode, die dies für U tut?

setPropertyFromParams(array $hashParamList) { ... }
Ulrich Tevi Horus
quelle
nicht gut für die automatische Vervollständigung und Refactoring und Lesbarkeit anb Boilerplate-Code
Andreas Dietrich
Denn: 1) Sie müssen sich die Reihenfolge merken oder sie aus den Dokumenten lesen, 2) Sie verlieren jegliche Sicherheit vom Typ der Kompilierungszeit, 3) Sie müssen die Werte umwandeln (dies und 2) ist natürlich kein Problem in einer Dynamik Sprache natürlich), 4) Sie können nicht diese nutzbringend andere erweitern als bei jedem Aufruf die Array-Länge überprüft, und 5) , wenn Sie etwas ändern, sei es bestellen oder sogar die Existenz bestimmter Werte, können Sie nicht , dass keiner der beiden Laufzeit überprüfen noch kompilieren zeitnah und Zweifel überhaupt . Mit Setzern: 1), 2), 3): Kein Problem. 4) Das Hinzufügen neuer Methoden macht nichts kaputt. 5) explizite Fehlermeldung.
Adowrath