让制定者返回“这个”是不好的做法吗?

时间:2009-08-28 04:21:59

标签: java design-patterns api oop

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

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

这种模式很有用,因为你可以链接这样的设置器:

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

......但这有点违反标准惯例。我想这可能是值得的,因为它可以让那个setter做一些有用的东西。我已经看到这种模式使用了一些地方(例如JMock,JPA),但它似乎并不常见,并且通常仅用于定义良好的API,其中此模式在任何地方都使用。

更新

我所描述的内容显然是有效的,但我真正想要的是关于这是否普遍可以接受,以及是否存在任何陷阱或相关最佳实践的一些想法。我知道Builder模式,但它比我描述的要多一点 - 正如Josh Bloch描述的那样,有一个关联的静态Builder类用于创建对象。

27 个答案:

答案 0 :(得分:96)

这不是一种糟糕的做法。这是一种越来越普遍的做法。如果您不想这样做,大多数语言都不要求您处理返回的对象,因此它不会更改“正常”的setter使用语法,但允许您将setter链接在一起。

这通常称为构建器模式或fluent interface

在Java API中也很常见:

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

答案 1 :(得分:81)

总结:

  • 它被称为“流畅的界面”,或“方法链”。
  • 这不是“标准”Java,虽然你现在看到它更多(在jQuery中运行得很好)
  • 它违反了JavaBean规范,因此它会破坏各种工具和库,特别是JSP构建器和Spring。
  • 它可能会阻止JVM通常会做的一些优化
  • 有些人认为它可以清理代码,有些人则认为它是“可怕的”

未提及的其他几点:

  • 这违反了每个函数应该做一个(并且只有一个)事情的原则。您可能会或可能不会相信这一点,但在Java中我认为它运作良好。

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

  • 我终于,这是一个真实的数据点。我使用像这样构建的库时遇到了问题。 Hibernate的查询构建器就是现有库中的一个示例。由于Query的set *方法正在返回查询,因此仅通过查看签名如何使用它是不可能的。例如:

    Query setWhatever(String what);
    
  • 它引入了歧义:该方法是否修改了当前对象(您的模式),或者,Query可能是不可变的(非常流行且有价值的模式),并且该方法返回一个新方法。它只是使库更难使用,许多程序员不利用这个功能。如果setter是setter,那么如何使用它会更清楚。

答案 2 :(得分:76)

我更喜欢使用'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!"));

答案 3 :(得分:70)

我认为它没有什么特别的错误,只是风格问题。它在以下情况下很有用:

  • 您需要一次设置多个字段(包括构建时)
  • 您知道在编写代码时需要设置哪些字段,
  • 您要设置的字段有许多不同的组合。

此方法的替代方案可能是:

  1. 一个巨型构造函数(缺点:您可能会传递大量的空值或默认值,并且很难知道哪个值对应于什么)
  2. 几个重载的构造函数(缺点:一旦你有多个构建器就变得笨重)
  3. 工厂/静态方法(缺点:与重载的构造函数相同 - 一旦存在多个构造函数就变得难以操作)
  4. 如果你只想一次设置一些房产,我会说不值得返回'这个'。如果您以后决定返回其他内容,例如状态/成功指示/消息,那肯定会失败。

答案 4 :(得分:25)

如果您不想从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!"
});

答案 5 :(得分:11)

它不仅打破了getter / setter的惯例,还打破了Java 8方法引用框架。 MyClass::setMyValueBiConsumer<MyClass,MyValue>myInstance::setMyValueConsumer<MyValue>。如果您的setter返回this,那么它不再是Consumer<MyValue>的有效实例,而是Function<MyValue,MyClass>,并且会使用对这些setter的方法引用(假设它们无效)方法)打破。

答案 6 :(得分:7)

我不懂Java,但我已经用C ++完成了。 其他人说它会使线条很长很难读, 但我已经很多次这样做了:

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

这更好:

list.add(
    new Employee("Jack Sparrow")
    .Id(1)
    .foo("bacon!"));
至少,我认为。但是如果你愿意的话,欢迎你给我一个可怕的程序员。而且我不知道你是否被允许在Java中做到这一点。

答案 7 :(得分:6)

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

答案 8 :(得分:6)

这种被称为“流畅的界面”的方案(双关语)现在变得非常流行。这是可以接受的,但这不是我的一杯茶。

答案 9 :(得分:5)

这根本不是一个坏习惯。但它与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();

答案 10 :(得分:5)

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

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

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

答案 11 :(得分:3)

我赞成有“这个”回报的二传手。我不在乎它是否符合豆类标准。对我来说,如果可以使用“=”表达式/语句,那么返回值的setter就可以了。

答案 12 :(得分:3)

Paulo Abrantes提供了另一种使JavaBean setter流畅的方法:为每个JavaBean定义一个内部构建器类。如果你使用的工具会被返回值的setter搞砸,那么Paulo的模式可能有所帮助。

答案 13 :(得分:2)

我以前更喜欢这种方法,但我已经决定反对它。

原因:

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

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

一如既往的味道。我只是喜欢“最不惊喜”的方法。

答案 14 :(得分:2)

此特定模式称为方法链接。 Wikipedia link,这有更多的解释和示例,说明它是如何在各种编程语言中完成的。

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

答案 15 :(得分:2)

如果你在整个应用程序中使用相同的约定,那么它似乎很好。

如果你的应用程序的现有部分使用标准约定,那么我会坚持使用它并将构建器添加到更复杂的类中

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

答案 16 :(得分:1)

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

如果我可以添加一些东西,那么这个问题:

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

答案 17 :(得分:1)

一般来说这是一个很好的做法,但你可能需要set-type函数使用Boolean类型来确定操作是否成功完成,这也是一种方法。一般来说,没有教条可以说这是好的还是床,当然是来自这种情况。

答案 18 :(得分:1)

乍一看:“可怕!”。

进一步思考

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

实际上比

更不容易出错
Employee anEmployee = new Employee();
anEmployee.setName("xxx");
...
list.add(anEmployee);

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

答案 19 :(得分:0)

这可能不太可读

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

答案 20 :(得分:0)

来自声明

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

我看到了两件事

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

答案 21 :(得分:0)

我已经让我的setter工作了很长一段时间,唯一真正的问题是库坚持使用严格的getPropertyDescriptors来获取bean读取器/编写器bean访问器。在这些情况下,你的java&#34; bean&#34;不会有你期望的写作。

例如,我没有对它进行过测试,但是当我从json / maps创建java对象时,杰克逊不会认识到这些是不知道的,我也不会感到惊讶。我希望我错了(我会尽快测试)。

事实上,我正在开发一个轻量级的SQL中心ORM,我必须将一些代码beyong getPropertyDescriptors添加到返回此功能的已识别的setter中。

答案 22 :(得分:0)

很久以前回答,但我的两分钱......很好。我希望更频繁地使用这种流畅的界面。

重复'factory'变量不会在下面添加更多信息:

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

这是更清洁,imho:

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

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

答案 23 :(得分:0)

如果可用,最好使用其他语言结构。例如,在Kotlin中,您可以使用 with apply let 。如果使用这种方法,您将不会需要从您的setter返回实例。

此方法允许您的客户端代码为:

  • 对返回类型无动于衷
  • 易于维护
  • 避免编译器副作用

以下是一些例子。

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

答案 24 :(得分:0)

如果我正在编写API,我会使用“return this”来设置仅设置一次的值。如果我有任何其他值,用户应该能够更改,我改为使用标准的void setter。

然而,这实际上是一个偏好问题,在我看来,链接制定者确实看起来很酷。

答案 25 :(得分:0)

我同意所有发布者的观点,声称这违反了JavaBeans规范。有理由保留它,但我也感到使用了这个构建器模式(已暗示)有其应有的地位。只要不是在所有地方都使用它,就应该可以接受。对我来说,“ It's Place”是对“ build()”方法的调用的终点。

当然,还有其他方法可以设置所有这些内容,但是这样做的好处是它避免了1)多参数公共构造函数和2)部分指定的对象。在这里,您让构建器收集了所需的内容,然后在最后调用其“ build()”,这可以确保未构建部分指定的对象,因为该操作的可见性不及公共。替代方案是“参数对象”,但是恕我直言,只是将问题推回了一个层次。

我不喜欢多参数构造函数,因为它们使传入许多相同类型的参数的可能性更大,这使得将错误的参数传递给参数更加容易。我不喜欢使用很多设置器,因为可以在完全配置对象之前使用该对象。此外,通过“ build()”方法可以更好地满足基于先前选择的默认值的概念。

简而言之,如果使用得当,我认为这是一个好习惯。

答案 26 :(得分:-4)

坏习惯: 一套二传手套装 getter get

如何明确声明一个方法,为U

setPropertyFromParams(array $hashParamList) { ... }