关于java:让setter返回“this”是不好的做法吗?

Is it bad practice to make a setter return “this”?

让Java中的设置器返回"这个"是好还是坏的想法?

1
2
3
4
public Employee setName(String name){
   this.name = name;
   return this;
}

这种模式很有用,因为这样就可以设置链:

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

而不是这个:

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

…但这有点违背了标准惯例。我想这可能是值得的,因为它可以使setter做一些其他有用的事情。我已经看到这个模式使用了一些地方(例如jmock、jpa),但它似乎并不常见,而且通常只用于定义很好的API,在这些API中,这个模式无处不在。

更新:

我所描述的显然是有效的,但我真正要寻找的是一些关于这是否一般可以接受的想法,以及是否存在任何陷阱或相关的最佳实践。我知道构建器模式,但它比我描述的要复杂一些——正如JoshBloch所描述的,有一个用于对象创建的关联静态构建器类。


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

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

在Java API中也是常见的:

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


总结:

  • 它被称为"fluent接口",或"方法链接"。
  • 这不是"标准"Java,虽然您现在看到的更多了(JQuery中的工作非常出色)
  • 它违反了JavaBean规范,因此它将使用各种工具和库,特别是JSP构建器和Spring。
  • 它可能会阻止一些JVM通常会做的优化
  • 有些人认为它会清除代码,另一些人则认为它是"可怕的"

还有几点没提到:

  • 这违背了原则,即每个函数都应该做一件(并且只有一件)事情。你可能相信,也可能不相信这一点,但在Java中,我相信它工作得很好。

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

  • 最后,这里是一个真实的数据点。我在使用像这样的图书馆时遇到了问题。Hibernate的查询生成器就是现有库中的一个例子。由于查询的set*方法正在返回查询,因此不可能仅仅通过查看签名就知道如何使用它。例如:

    1
    Query setWhatever(String what);
  • 它引入了一种歧义:方法是否修改了当前对象(您的模式),或者查询是否真的是不可变的(一种非常流行和有价值的模式),并且该方法正在返回一个新的模式。它只是使库更难使用,而且许多程序员不利用这个特性。如果设置者是设置者,那么就更清楚如何使用它了。


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

1
2
3
4
5
6
public String getFoo() { return foo; }
public void setFoo(String foo) { this.foo = foo; }
public Employee withFoo(String foo) {
  setFoo(foo);
  return this;
}

因此:

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


我不认为它有什么特别的问题,只是风格问题。当:

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

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

  • 一个大型构造函数(缺点:您可能传递大量的空值或默认值,很难知道哪个值对应于什么)
  • 几个重载的构造函数(缺点:一旦拥有多个构造函数,就会变得笨拙)
  • 工厂/静态方法(缺点:与重载构造函数相同-一旦有多个构造函数就会变得笨拙)
  • 如果一次只设置几个属性,我会说不值得返回"this"。如果您以后决定返回其他内容,比如状态/成功指示器/消息,那么它肯定会下降。


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

    1
    2
    3
    4
    5
    6
    list.add(new Employee()
    {{
        setName("Jack Sparrow");
        setId(1);
        setFoo("bacon!");
    }});

    作为旁白,我认为它在C中稍微干净一点:

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


    它不仅打破了GETS/SETTER的约定,也打破了Java 8方法参考框架。MyClass::setMyValueBiConsumermyInstance::setMyValueConsumer。如果您的setter返回this,那么它不再是Consumer的有效实例,而是Function,并且会导致使用这些setter的方法引用(假定它们是无效方法)的任何内容中断。


    我不知道Java,但我已经用C++完成了。有人说这句话很长很难读,但我已经这样做了很多次了:

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

    这甚至更好:

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

    至少,我认为。但如果你愿意的话,欢迎你投我一票,叫我糟糕的程序员。我不知道你是否可以用Java做这个。


    因为它不返回void,所以它不再是有效的JavaBean属性设置器。如果您是世界上使用可视化"bean builder"工具的七个人之一,或者是使用jsp bean setproperty元素的17个人之一,那么这可能很重要。


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


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

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

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


    这一点也不坏。但它与JavaBeans规范不兼容。

    有很多规范依赖于这些标准的访问器。

    你可以让它们彼此共存。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
    }

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

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

    下面是不可变对象的另一个版本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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;
    }

    现在我们可以这样做了。

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


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


    PauloAbrantes提供了另一种让JavaBean setter流畅的方法:为每个JavaBean定义一个内部生成器类。如果您使用的工具被返回值的setter所迷惑,那么Paulo的模式可能会有所帮助。


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

    原因:

    • 可读性。将每个setfoo()放在一个单独的行上会使代码更可读。通常,你读代码的次数比你一次写代码的次数多很多。
    • 副作用:set foo()只应设置字段foo,而不应设置其他值。返回这是一个额外的"那是什么"。

    我看到的构建器模式不使用setfoo(foo).setbar(bar)约定,而是使用更多的foo(foo).bar(bar)。也许正是因为这些原因。

    这是,一如既往的品味问题。我只是喜欢"最不意外"的方法。


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

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


    如果你在整体上使用相同的惯例,这似乎是好的。

    另一方面,如果应用程序的现有部分使用标准约定,我将坚持使用它,并将构建器添加到更复杂的类中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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;
        }
    }

    这将起作用:

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

    这不会被Eclipse接受!:

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

    这是因为setname()返回的是人而不是朋友,并且没有peoplesetnickname。

    我们如何编写setters来返回self类而不是类的名称?

    类似这样的事情会很好(如果存在self关键字的话)。这到底存在吗?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class People
    {
        private String name;
        public SELF setName(String name)
        {
            this.name = name;
            return this;
        }
    }


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


    乍一看:"可怕!".

    关于进一步的思考

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

    实际上比

    1
    2
    3
    4
    Employee anEmployee = new Employee();
    anEmployee.setName("xxx");
    ...
    list.add(anEmployee);

    非常有趣。正在向工具包添加想法…


    我同意所有声称这违反了JavaBeans规范的海报。有理由保留这一点,但我也认为使用这种构建器模式(这是暗指)是有其用武之地的;只要它不在任何地方使用,它应该是可以接受的。"在我看来,这是一个地方,它的终点是对"build()"方法的调用。

    当然,还有其他设置所有这些内容的方法,但是这里的优势在于它避免了1)许多参数公共构造函数和2)部分指定的对象。这里,让构建器收集所需的内容,然后在末尾调用它的"build()",这样可以确保不构建部分指定的对象,因为该操作的可见性可以低于公共可见性。另一种选择是"参数对象",但imho只是将问题向后推一级。

    我不喜欢许多参数构造函数,因为它们使传入许多相同类型参数的可能性更大,这可以使向参数传递错误参数更容易。我不喜欢使用很多setter,因为对象可以在完全配置之前使用。此外,基于前面的选择拥有默认值的概念更适合使用"build()"方法。

    简而言之,如果使用得当,我认为这是一个很好的做法。


    我制作setter已经有一段时间了,唯一真正的问题是库中坚持使用严格的getPropertyDescriptor来获取bean读写器bean访问器。在这些情况下,您的Java"bean"将不具有您所期望的写入器。

    例如,我还没有对它进行过测试,但我不会惊讶于当杰克逊从JSON/MAP创建Java对象时,不会识别这些设置器。我希望我在这个问题上是错的(我很快就会测试它)。

    事实上,我正在开发一个轻量级的以SQL为中心的ORM,并且我必须在返回这个消息的公认的setter中添加一些超越getpropertydescriptor的代码。


    从声明中

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

    我看到两件事

    1)无意义陈述。2)可读性不足。


    很久以前的答案,但我的两分钱…很好。我希望这个流畅的界面能经常使用。

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

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

    这更干净,imho:

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

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


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

    这种方法允许您的客户机代码是:

    • 与返回类型无关
    • 易于维护
    • 避免编译器副作用

    下面是一些例子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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"
    }


    如果我正在编写一个API,我使用"返回这个"来设置只设置一次的值。如果我有任何其他用户应该能够更改的值,我将使用标准的void setter。

    然而,这真的是一个偏好的问题,在我看来,链接设置器看起来确实很酷。


    这可能不太可读

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

    或者这个

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

    这比:

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


    坏习惯:设置器吸气器

    那么显式声明一个方法呢,这样做是为了u

    1
    setPropertyFromParams(array $hashParamList) { ... }