让setter返回“this”是不好的做法?

在java中使setter返回“this”是好的还是坏主意?

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

这种模式可以是有用的,因为那样你可以像这样链接setter:

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

而不是这个:

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

…但有点违背标准惯例。 我认为这可能是值得的,因为它可以使这个二传手做一些有用的事情。 我已经看到这种模式使用了一些地方(如JMock,JPA),但它似乎并不常见,并且通常只用于定义非常好的API,这种模式在任何地方都可以使用。

更新:

我所描述的显然是有效的,但是我真正想要的是关于这是否普遍接受的一些想法,以及是否有任何陷阱或相关的最佳实践。 我知道关于Builder的模式,但是我所描述的更多一点 – 就像Josh Bloch描述的那样,它有一个关联的静态生成器类来创build对象。

我不认为有什么特别的错误,这只是一个风格问题。 在以下情况下有用:

  • 你需要立即设置很多领域(包括在build设中)
  • 你知道你在编写代码的时候需要设置哪些字段
  • 有很多不同的组合,你想设置哪些字段。

这种方法的替代scheme可能是:

  1. 一个巨型的构造函数(不足之处在于:你可能会传递大量的空值或默认值,并且很难知道哪个值对应于哪个值)
  2. 几个重载的构造函数(不足之处:一旦你有更多的构造函数会变得笨拙)
  3. 工厂/静态方法(缺点:与重载的构造函数相同 – 一旦有更多的构造函数就会变得笨拙)

如果你一次只设置一些属性,我会说这不值得返回“this”。 如果你之后决定退还其他东西,比如状态/成功指示器/信息,它肯定会下降。

这不是一个坏习惯。 这是一个日益普遍的做法。 大多数语言不要求你处理返回的对象,如果你不想这样做,它不会改变“正常”的setter使用语法,但可以让你连接setter。

这通常被称为构build器模式或stream畅的接口 。

在Java API中也很常见:

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

总结:

  • 它被称为“stream畅的接口”或“方法链接”。
  • 这不是“标准的”Java,尽pipe你现在看到的更多(在jQuery中很棒)
  • 它违反了JavaBean规范,因此它将与各种工具和库,特别是JSP构build器和Spring打破。
  • 它可能会阻止JVM正常执行的某些优化
  • 一些人认为它清理代码,另一些人认为这是“可怕的”

其他几点没有提到:

  • 这违背了每个函数应该做一个(而且只有一个)事情的原则。 你可能会也可能不会相信,但在Java中,我相信它运作良好。

  • IDE不会为您生成这些(默认情况下)。

  • 最后,这是一个真实世界的数据点。 使用这样构build的库,我遇到了问题。 Hibernate的查询生成器就是现有库中的一个例子。 由于Query的set *方法正在返回查询,所以仅仅通过查看签名如何使用它是不可能的。 例如:

     Query setWhatever(String what); 
  • 它引入了一个模棱两可的问题:方法是修改当前对象(你的模式)还是Query可能是不可变的(一个非常stream行和有价值的模式),并且方法返回一个新的。 它只是使图书馆更难使用,许多程序员不利用这个function。 如果安装者是安装者,那么如何使用安装者会更清楚。

我更喜欢使用'with'方法:

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

从而:

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

如果您不希望从setter中返回'this' ,但不想使用第二个选项,则可以使用以下语法来设置属性:

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

顺便说一下,我认为它在C#中稍微干净一些:

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

我不知道Java,但我已经在C ++中做到了这一点。 其他人都说,这使得行数很长,很难阅读,但我做了很多次:

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

这更好:

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

至less,我想。 但是,如果你愿意的话,欢迎你给我打电话,给我一个可怕的程序员。 我不知道你是否被允许在Java中这样做。

因为它不返回void,所以它不再是有效的JavaBean属性设置器。 如果你是世界上使用可视化的“Bean Builder”工具的七个人之一,或者使用JSP-bean-setProperty元素中的一个,那么这可能很重要。

它不仅打破了getter / setter的约定,而且打破了Java 8方法的参考框架。 MyClass::setMyValue是一个BiConsumer<MyClass,MyValue>myInstance::setMyValue是一个Consumer<MyValue> 。 如果你的setter返回this ,那么它不再是Consumer<MyValue>一个有效实例,而是一个Function<MyValue,MyClass> ,并且会导致任何对这些设置器的方法引用(假设它们是void方法) 。

这根本不是一个坏习惯。 但是它与JavaBeans Spec不兼容。

还有很多规范取决于这些标准访问者。

你总是可以让它们相互并存。

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

现在我们可以一起使用它们。

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

这里是另一个不可变对象的版本。

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

现在我们可以做到这一点。

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

这个被称为“stream畅界面”的scheme(双关意图)现在正变得非常stream行。 这是可以接受的,但这不是我的一杯茶。

至less在理论上 ,它可以通过在调用之间设置错误的依赖关系来破坏JVM的优化机制。

它应该是语法糖,但实际上可以在超级智能Java 43的虚拟机中产生副作用。

这就是为什么我不投票,不要使用它。

我赞成有“这个”返回的setter。 我不在乎,如果它不符合豆类。 对我来说,如果可以使用“=”expression式/语句,那么返回值的设置器就可以了。

我曾经喜欢这种方法,但我决定反对。

原因:

  • 可读性。 它使代码更具可读性,让每个setFoo()在一个单独的行上。 您通常阅读代码的次数比单次编写代码多很多倍。
  • 副作用:setFoo()应该只设置字段富,没有别的。 回到这是一个额外的“那是什么”。

我看到的Builder模式不使用setFoo(foo).setBar(bar)约定,而是使用更多foo(foo).bar(bar)。 也许正是这些原因。

这是一如既往的品味问题。 我只是喜欢“最less的惊喜”的方法。

Paulo Abrantes提供了使JavaBean setterstream畅的另一种方法:为每个JavaBean定义一个内部生成器类。 如果你使用的工具会被返回值的setter所阻塞,Paulo的模式可能会有所帮助。

一见钟情:“可怕!”

进一步思考

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

实际上比较不容易出错

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

非常有趣 将想法添加到工具包…

是的,我认为这是一个好主意。

如果我能添加一些东西,那么这个问题呢?

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

这将工作:

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

这不会被Eclipse接受! :

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

这是因为setName()返回一个People而不是Friend,并且没有PeoplesetNickName。

我们如何编写setter来返回SELF类而不是类的名字?

这样的事情会没事的(如果SELF关键字存在)。 这是否存在?

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

这个特定的模式被称为方法链接。 维基百科链接 ,这有更多的解释和如何在各种编程语言完成的例子。

PS:只是想到把它留在这里,因为我正在寻找具体的名字。

如果你在整个应用程序中使用相同的惯例,似乎很好。

另一方面,如果你的应用程序的现有部分使用标准约定,我会坚持它,并添加build设者到更复杂的类

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

一般来说,这是一个好习惯,但是您可能需要set-type函数使用布尔types来确定操作是否成功完成,这也是一种方法。 一般来说,没有教条说这是好的或是床上的,当然是出于情况。

从声明

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

我看到两件事

1)无意义的陈述。 2)缺乏可读性。

这可能不太可读

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

或这个

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

这比以下方式更可读:

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

我一直在制作setter,并且唯一真正的问题是使用严格的getPropertyDescriptors来获得bean读写器的bean访问器。 在这些情况下,你的java“bean”将不会有你期望的写入器。

例如,我没有testing它,但是当从json / maps创buildjava对象时,Jackson不会将这些作为setter识别出来。 我希望我在这一个错误(我会尽快testing)。

实际上,我正在开发一个轻量级的以SQL为中心的ORM,并且我必须添加一些beyong getPropertyDescriptors代码给可识别的设置器,它返回这个值。

很久以前的答案,但我的两分钱…它的罚款。 我希望这个stream畅的界面更经常使用。

重复“工厂”variables不会在下面添加更多信息:

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

这是更清洁,恕我直言:

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

当然,作为已经提到的答案之一,在某些情况下,如inheritance和工具,必须调整Java API才能正确执行此操作。

如果可用,最好使用其他语言结构。 例如,在Kotlin中,你可以使用, 应用 。 如果使用这种方法,你不会真的需要从你的setter中返回一个实例。

这种方法允许你的客户端代码是:

  • 漠不关心的回报types
  • 更容易维护
  • 避免编译器的副作用

这里有些例子。

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

坏习惯:二传手设置一个获得者

那么显式声明一个方法是什么呢?

 setPropertyFromParams(array $hashParamList) { ... }