代码之家  ›  专栏  ›  技术社区  ›  Ken Liu

让二传手回“这个”是不是不好?

  •  220
  • Ken Liu  · 技术社区  · 15 年前

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

    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,在那里这个模式到处都在使用。

    更新:

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

    27 回复  |  直到 6 年前
        1
  •  68
  •   Tom Clift    15 年前

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

    • 您需要同时设置多个字段(包括在构造时)
    • 你知道在编写代码时需要设置哪些字段,并且
    • 有许多不同的组合要为其设置字段。

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

    1. 一个巨型构造函数(缺点:可能会传递大量的空值或默认值,很难知道哪个值对应于什么)
    2. 多个重载构造函数(缺点是:一旦拥有多个构造函数,就会变得笨拙)
    3. 工厂/静态方法(缺点:与重载构造函数相同-一旦有多个构造函数,就会变得笨拙)

    如果你一次只设置几个属性,我会说不值得返回“this”。如果您稍后决定返回其他内容(如状态/成功指示器/消息),则肯定会失败。

        2
  •  95
  •   cletus    15 年前

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

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

    在Java API中也是常见的:

    String s = new StringBuilder().append("testing ").append(1)
      .append(" 2 ").append(3).toString();
    
        3
  •  76
  •   falsarella Pixelomo    12 年前

    总结一下:

    • 它被称为“流畅的接口”,或“方法链”。
    • 这不是“标准”Java,虽然您现在看到的更多了(JQuery中的工作非常出色)
    • 它违反了javabean规范,因此它将破坏各种工具和库,特别是jsp构建器和spring。
    • 它可能会阻止一些jvm通常会做的优化
    • 有人认为它会清理代码,也有人认为它“可怕”

    还有几点没有提到:

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

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

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

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

        4
  •  75
  •   qualidafial    11 年前

    我更喜欢使用“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!"));
    
        5
  •  24
  •   Luke Quinane George    11 年前

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

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

    作为旁白,我认为它在c:

    list.Add(new Employee() {
        Name = "Jack Sparrow",
        Id = 1,
        Foo = "bacon!"
    });
    
        6
  •  9
  •   Steve K    9 年前

    它不仅打破了GETS/SETTER的约定,也打破了Java 8方法参考框架。 MyClass::setMyValue 是一个 BiConsumer<MyClass,MyValue> myInstance::setMyValue 是一个 Consumer<MyValue> . 如果你的二传手回来了 this ,则它不再是 消费者<我的价值> ,但更确切地说 Function<MyValue,MyClass> ,并将导致使用对这些setter的方法引用(假设它们是void方法)的任何内容中断。

        7
  •  7
  •   Carson Myers    15 年前

    我不知道Java,但我已经用C++完成了。 其他人说这会让台词很长很难读, 但我已经这样做过很多次了:

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

    这样更好:

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

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

        8
  •  6
  •   Ken    15 年前

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

        9
  •  5
  •   Noon Silk    15 年前

    这种被称为“流畅界面”的方案(双关语)现在变得相当流行。这是可以接受的,但这不是我真正喜欢的。

        10
  •  5
  •   Paul Vargas    11 年前

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

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

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

        11
  •  5
  •   Jin Kwon    7 年前

    这一点也不坏。但它与 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();
    
        12
  •  3
  •   Monty Hall    14 年前

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

        13
  •  3
  •   entpnerd Shahid Hussain Abbasi    7 年前

    Paulo Abrantes 提供了另一种使javabean setters流畅的方法:为每个javabean定义一个内部生成器类。如果您使用的工具被返回值的setter弄糊涂了,paulo的模式可能会有帮助。

        14
  •  2
  •   Thorbjørn Ravn Andersen    15 年前

    我过去喜欢这种方法,但我已决定反对。

    原因:

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

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

    这是一个品味问题。我只是喜欢“最不意外”的方法。

        15
  •  2
  •   capt.swag    7 年前

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

    附言:我想把它留在这里,因为我在找具体的名字。

        16
  •  2
  •   Marcin Szymczak    7 年前

    如果你在整个应用程序中使用相同的约定,这看起来很好。

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

    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;
        }
    }
    
        17
  •  1
  •   djna    15 年前

    一见钟情:“可怕!”.

    进一步思考

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

    实际上比

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

    真有趣。正在将想法添加到工具包…

        18
  •  1
  •   Baptiste    12 年前

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

    如果我能补充一下,这个问题呢:

    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()返回的是一个人,而不是一个朋友,并且没有peoplesetnickname。

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

    如果self关键字存在的话,这样就可以了。这是真的吗?

    class People
    {
        private String name;
        public SELF setName(String name)
        {
            this.name = name;
            return this;
        }
    }
    
        19
  •  1
  •   Narek    7 年前

    一般来说,这是一个很好的实践,但是您可能需要set type函数使用布尔类型来确定操作是否成功完成,这也是一种方法。一般来说,没有教条说这是好还是床,当然是来自于形势。

        20
  •  0
  •   LiraNuna    15 年前

    从声明中

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

    我看到两件事

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

        21
  •  0
  •   falsarella Pixelomo    12 年前

    这可能不太可读

    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); 
    
        22
  •  0
  •   Jeremy Chone    10 年前

    我已经做了很长一段时间的setter,唯一真正的问题是使用严格的getpropertydescriptors获取bean读写器bean访问器的库。在这些情况下,您的Java“bean”将不具有您所期望的写入器。

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

    事实上,我正在开发一个轻量级的以sql为中心的orm,我必须添加一些getpropertydescriptors之外的代码到返回这个的公认setter中。

        23
  •  0
  •   Josef.B    9 年前

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

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

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

    这更干净,伊姆霍:

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

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

        24
  •  0
  •   Steven Spungin    7 年前

    如果可以,最好使用其他语言结构。例如,在Kotlin中,您可以使用 具有 , 应用 . 如果用这种方法,你不会真的 需要 从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"
    }
    
        25
  •  0
  •   Kaiser Keister    6 年前

    如果我正在编写一个api,我使用“return this”来设置只设置一次的值。如果我有任何其他用户可以更改的值,我将使用标准void setter。

    不过,这确实是一个偏好的问题,在我看来,链式二传手确实很酷。

        26
  •  0
  •   Javaneer    6 年前

    我同意所有声称这违反了javabeans规范的海报。有理由保留这一点,但我也觉得使用这个构建器模式(被暗指)是有它的位置的;只要它不是在所有地方都使用,就应该是可以接受的。”对我来说,它的终点是对“build()”方法的调用。

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

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

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

        27
  •  -4
  •   Ulrich Tevi Horus    8 年前

    坏习惯: 设置器 吸气器

    那么显式声明一个方法呢,它是为你做的

    setPropertyFromParams(array $hashParamList) { ... }