让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可能是:
- 一个巨型的构造函数(不足之处在于:你可能会传递大量的空值或默认值,并且很难知道哪个值对应于哪个值)
- 几个重载的构造函数(不足之处:一旦你有更多的构造函数会变得笨拙)
- 工厂/静态方法(缺点:与重载的构造函数相同 – 一旦有更多的构造函数就会变得笨拙)
如果你一次只设置一些属性,我会说这不值得返回“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) { ... }