【问题标题】:Is it bad practice to make a setter return "this"?让二传手返回“this”是不好的做法吗?
【发布时间】:2010-11-23 14:51:53
【问题描述】:

让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 类用于对象创建。

【问题讨论】:

  • 自从我前段时间看到这种设计模式以来,我尽可能地这样做。如果一个方法不需要显式地返回一些东西来完成它的工作,它现在返回this。有时,我什至会更改函数,以便它对对象的成员进行操作,而不是返回值,这样我就可以做到这一点。太棒了。 :)
  • 对于返回自我的伸缩式安装器和在构建器中,我更喜欢使用 withName(String name) 而不是 setName(String name)。正如您指出的那样,setter 的常见做法和期望是返回 void。 “非标准”设置器在现有框架中可能表现不佳,例如JPA 实体管理器、Spring 等
  • 请在每次调用之前引入换行符 :) 并配置您的 IDE,或者如果它不遵守这一点,请获得一个合适的。
  • 广泛使用的框架(例如 Spring 和 Hibernate)将严格(至少他们曾经)遵守 void-setters 约定

标签: java oop design-patterns


【解决方案1】:

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

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

在 Java API 中也很常见:

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

【讨论】:

  • 它经常在构建器中使用,但我不会说“这是......称为构建器模式”。
  • 对我来说很有趣的是,流畅接口的一些基本原理是它们使代码更易于阅读。我可以看到它写起来更方便,但我觉得它更难阅读。这是我唯一真正的分歧。
  • 它也被称为 train-wreck 反模式。问题是当空指针异常堆栈跟踪包含这样的一行时,您不知道哪个调用返回了空值。这并不是说应该不惜一切代价避免链接,而是要提防糟糕的库(尤其是自制的)。
  • @ddimitrov 只要您将其限制为返回 this 就不会成为问题(只有第一次调用可能会抛出 NPE)
  • 编写和阅读更容易假设您将换行符和缩进放在可读性会受到影响的地方! (因为当您可以编写 bla.foo /* newline indented */.setBar1(...) /* underneath previous setter */ .setBar2(...) 时,它避免了像 bla.foo.setBar1(...) ; bla.foo.setBar2(...) 这样的重复代码的冗余混乱(不能在这样的 SO 注释中使用换行符 :-( ...希望您在考虑 10 个这样的设置器或更复杂的情况下明白这一点)电话)
【解决方案2】:

总结一下:

  • 它被称为“流利的接口”或“方法链”。
  • 这不是“标准”Java,尽管这些天你看到的越来越多(在 jQuery 中效果很好)
  • 它违反了 JavaBean 规范,因此它会破坏各种工具和库,尤其是 JSP 构建器和 Spring。
  • 它可能会阻止一些 JVM 通常会进行的优化
  • 有些人认为它可以清理代码,有些人认为它“可怕”

还有几点没有提到:

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

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

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

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

【讨论】:

  • 顺便说一句,它是“流利的”,而不是“流畅的”......因为它可以让您像口语一样构建一系列方法调用。
  • 关于不变性的最后一点非常重要。最简单的例子是字符串。 Java 开发人员期望在 String 上使用方法时,他们会获得全新的实例,而不是相同但修改过的实例。使用流畅的接口,必须在方法文档中提到返回的对象是“this”而不是新实例。
  • 虽然我总体上同意,但我不同意这违反了“只做一件事”的原则。返回this 几乎不是复杂的火箭科学。 :-)
  • 补充一点:也违反了Command-query分离原则。
【解决方案3】:

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

警告withX 语法通常用于为不可变对象提供“设置器”,因此这些方法的调用者可能合理地期望它们创建新对象而不是改变现有实例。也许更合理的措辞是这样的:

list.add(new Employee().chainsetName("Jack Sparrow")
                       .chainsetId(1)
                       .chainsetFoo("bacon!"));

使用 chainsetXyz() 命名约定,几乎每个人都应该感到高兴。

【讨论】:

  • +1 用于有趣的约定。我不会在我自己的代码中采用它,因为现在您似乎必须为每个类字段拥有一个get、一个set 和一个with。不过,这仍然是一个有趣的解决方案。 :)
  • 这取决于你调用setter的频率。我发现如果这些设置器被调用了很多,添加它们的额外麻烦是值得的,因为它简化了其他地方的代码。 YMMV
  • 如果你将它添加到Project Lombok@Getter/@Setter 注释中......这对于链接来说太棒了。或者您可以使用类似于 JQuery 和 Javascript 恶魔使用的 Kestrel 组合器 (github.com/raganwald/Katy) 的东西。
  • @AlikElzin-kilaka 实际上我只是注意到 Java 8 中的 java.time 不可变类使用这种模式,例如LocalDate.withMonth、withYear 等
  • with 前缀是不同的约定。正如@qualidafial 举了一个例子。以with 为前缀的方法不应返回this,而是返回一个新实例,如当前实例,但with 会发生变化。当您希望您的对象是不可变的时,就会这样做。所以当我看到一个以with 为前缀的方法时,我假设我会得到一个新对象,而不是同一个对象。
【解决方案4】:

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

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

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

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

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

【讨论】:

  • 好吧,按照惯例,通常你不会从二传手返回任何东西。
  • 也许不是一开始,但 setter 不一定保持其最初的目的。过去的变量可能会变成包含多个变量或具有其他副作用的状态。一些设置器可能会返回以前的值,如果失败对于异常来说太常见,其他设置器可能会返回失败指示符。不过,这提出了另一个有趣的观点:如果您使用的工具/框架在具有返回值时无法识别您的设置器怎么办?
  • @Tom 好点,这样做打破了 getter 和 setter 的“Java bean”约定。
  • @TomClift 打破“Java Bean”约定会导致任何问题吗?是使用“Java Bean”约定查看返回类型或仅查看方法参数和方法名称的库。
  • 这就是 Builder 模式存在的原因,setter 不应该返回一些东西,相反,如果需要的东西看起来更好并且需要更少的代码,就创建一个 builder :)
【解决方案5】:

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

【讨论】:

  • 双大括号初始化可能对equals有问题,因为它创建了一个匿名内部类;见c2.com/cgi/wiki?DoubleBraceInitialization
  • @Csaba_H 显然,这个问题是搞砸了equals 方法的人的错。如果您知道自己在做什么,那么在equals 中有非常干净的方法可以处理匿名类。
  • 为此创建一个新的(匿名)类?每个实例?
【解决方案6】:

它不仅打破了 getter/setter 的约定,还打破了 Java 8 方法参考框架。 MyClass::setMyValueBiConsumer<MyClass,MyValue>myInstance::setMyValueConsumer<MyValue>。如果你的设置器返回this,那么它不再是Consumer<MyValue> 的有效实例,而是Function<MyValue,MyClass>,并且将导致使用对这些设置器的方法引用(假设它们是无效方法)的任何操作中断。

【讨论】:

  • 如果 Java 有某种方法可以通过返回类型进行重载,而不仅仅是 JVM,那就太棒了。您可以轻松绕过其中许多重大更改。
  • 您始终可以通过提供void accept(A a) { apply(a); } 的默认实现来定义扩展Consumer<A>Function<A,B> 的功能接口。然后它可以很容易地用作任何一个,并且不会破坏任何需要特定表单的代码。
  • 啊! 这完全是错误的! 这就是所谓的 void-compatibility。返回值的 setter 可以充当 Consumer。 ideone.com/ZIDy2M
【解决方案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 中这样做。

【讨论】:

  • “更好”并不适合现代 IDE 中可用的格式化源代码功能。不幸的是。
  • 你可能是对的...我使用的唯一自动格式化程序是 emacs 的自动缩进。
  • 源代码格式化程序可以在链中的每个方法调用之后用一个简单的 // 强制执行。它会使您的代码有些丑陋,但不会像水平方向重新格式化您的垂直系列语句那样严重。
  • @qualidafial 如果您将 IDE 配置为不加入已包装的行(例如 Eclipse > Properties > Java > Code Style > Formatter > Line Wrapping > Never join,则在每个方法之后不需要 //已经换行了)。
【解决方案8】:

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

本应是语法糖,但实际上会在超智能Java 43的虚拟机中产生副作用。

这就是我投反对票的原因,不要使用它。

【讨论】:

  • 有趣...你能扩展一下吗?
  • 想想超标量处理器如何处理并行执行。执行第二个set 方法的对象依赖于第一个set 方法,尽管程序员知道它。
  • 我还是没跟上。如果使用两个单独的语句设置 Foo 和 Bar,则设置 Bar 的对象与设置 Foo 的对象具有不同的状态。所以编译器也不能并行化这些语句。至少,如果不引入无根据的假设,我看不到它怎么可能。(因为我不知道,我不会否认 Java 43 实际上确实在一次情况下进行了并行化,但在另一种情况下却没有,并引入在一种情况下没有根据的假设,但在另一种情况下则不然)。
  • 如果您不知道,请测试。 -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining java7 jdk 肯定内联了链式方法,并且执行与将 void setter 标记为热并内联它们所需的迭代次数大致相同。我认为你低估了 JVM 的操作码修剪算法的威力;如果它知道你正在返回它,它将跳过 jrs(java return 语句)操作码,并将它留在堆栈上。
  • Andreas,我同意,但是当你有一层又一层的低效代码时就会出现问题。 99% 的时间你应该为了清楚起见而编写代码,这在此处被说了很多。但有时您也需要切合实际,并利用您多年的经验在一般架构意义上过早地进行优化。
【解决方案9】:

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

【讨论】:

  • 我的编辑被拒绝了,但您的 Builder 示例不正确。首先, .value() 不返回任何内容,甚至不设置 some 字段。其次,您应该在 build() 中添加保护措施并将some 设置为null,这样Some 是真正不可变的,否则您可以在同一个Builder 实例上再次调用builder.value()。最后,是的,您有一个构建器,但是您的Some 仍然有一个公共构造器,这意味着您不公开提倡使用构建器,即用户除了尝试或搜索一种方法之外不知道它完全设置自定义value
  • @Adowrath 如果答案不正确,您应该编写自己的答案,而不是尝试编辑别人的形状
  • @JinKwon 太好了。谢谢!对不起,如果我以前看起来很粗鲁。
  • @Adowrath 请随时为任何其他 cmets 进行增强。仅供参考,我不是拒绝您编辑的人。 :)
  • 我知道,我知道。 ^^ 感谢新版本,它现在提供了一个真正可变的“不可变的”构建器。这是一个比我的编辑尝试更聪明的解决方案,相比之下,我的编辑尝试使代码混乱。
【解决方案10】:

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

【讨论】:

  • 如果你使用像 Spring 这样的 bean-aware 框架也很重要。
【解决方案11】:

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

【讨论】:

    【解决方案12】:

    如果您在整个应用程序中使用相同的约定,那似乎没问题。

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

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

    【讨论】:

    • 这正是 Builder 模式旨在解决的问题。它不会破坏 Java 8 中的 lambda,它不会破坏挑剔的 JavaBeans 工具,并且不会在 JVM 中引起任何优化问题(因为该对象仅在实例化期间存在)。它还解决了由于不使用构建器而导致的“构造器过多”问题,并消除了双括号匿名类的堆污染。
    • 有趣的一点 - 我注意到,如果你的类中几乎没有什么是不可变的(例如高度可配置的 GUI),那么你可能完全可以避免使用 Builder。
    【解决方案13】:

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

    【讨论】:

    【解决方案14】:

    我赞成二传手拥有“this”回报。我不在乎它是否不符合 bean。对我来说,如果可以使用“=”表达式/语句,那么返回值的设置器就可以了。

    【讨论】:

      【解决方案15】:

      我以前更喜欢这种方法,但我决定不这样做。

      原因:

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

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

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

      【讨论】:

      • 我同意副作用。返回东西的二传手违反了他们的名字。你正在设置 foo,但你得到了一个对象?这是一个新对象还是我改变了旧对象?
      【解决方案16】:

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

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

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

      【讨论】:

      • 除了 Eclipse 之外,还有一些其他的 Java 编译器不接受 :)。基本上,您正在运行 Java 类型系统的家禽(它是静态的,不像某些脚本语言那样是动态的):您必须先将 setName() 的内容转换为 Friend,然后才能对其进行 setNickName()。因此,不幸的是,对于继承层次结构,这消除了大部分可读性优势,并使这种原本可能有用的技术变得不那么有用。
      • 使用泛型。 class Chainable { public Self doSomething(){return (Self)this;} } 从技术上讲,它不是类型安全的(如果你用你无法做到的 Self 类型实现类,它将进行类转换),但它是正确的语法,子类会返回自己的类型。
      • 另外:Baptiste 使用的这种“SELF”被称为 self 类型,在许多语言中都不存在(Scala 是我现在唯一能想到的),而 Ajax 使用泛型是所谓的“Curiously recurring template pattern”,它试图解决缺少 self 类型的问题,但它有一些缺点:(来自class C<S extends C<S>>,它比普通的S extends C 更安全。a)如果A extends BB extends C<B>,并且你有一个 A,你只知道返回一个 B,除非 A 覆盖每个方法。 b) 你不能表示一个本地的、非原始的C<C<C<C<C<...>>>>>
      【解决方案17】:

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

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

      【讨论】:

        【解决方案18】:

        第一眼:“可怕!”。

        进一步思考

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

        实际上比

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

        非常有趣。在工具包中添加想法...

        【讨论】:

        • 不,仍然很可怕。从维护的角度来看,后者更好,因为它更容易阅读。此外,像 CheckStyle 这样的自动代码检查器将默认强制行为 80 个字符 - 无论如何代码都会被包装,加剧了可读性/可维护性问题。最后 - 它是 Java;无论如何,当它要被编译为字节码时,将所有内容写在一行上没有任何好处。
        • 就我个人而言,我认为前者更容易阅读,尤其是当您以这种方式创建多个对象时。
        • @Ken:编写方法。用流利的格式写一份;另一个副本中的另一个。现在将两份副本分给几个人,并询问他们觉得哪一份更容易阅读。阅读速度更快,编码速度更快。
        • 像大多数工具一样,它很容易被过度使用。 JQuery 是围绕这种技术的,因此很容易出现长调用链,我发现这实际上会损害可读性。
        • 如果在每个点之前有换行符并缩进就可以了。然后,它会像第二个版本一样可读。其实更多,因为开头没有多余的list
        【解决方案19】:

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

        【讨论】:

        • 如何使用异常来指示错误情况?正如许多 C 程序员痛苦地学到的那样,错误代码很容易被忽略。异常可以在堆栈中冒泡到可以处理的程度。
        • 通常最好使用异常,但是当您不能使用异常时,错误代码也很有用。
        • 嗨@Narek,也许您可​​以详细说明在什么情况下最好在设置器中使用错误代码而不是异常,为什么?
        【解决方案20】:

        来自声明

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

        我看到了两件事

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

        【讨论】:

          【解决方案21】:

          这可能不太可读

          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】:

          我已经做了很长一段时间的设置器,唯一真正的问题是坚持使用严格的 getPropertyDescriptors 来获取 bean 读取器/写入器 bean 访问器的库。在这些情况下,您的 java“bean”将没有您期望的写入器。

          例如,我还没有确定地对其进行测试,但如果 Jackson 在从 json/maps 创建 java 对象时不会将它们识别为 setter,我不会感到惊讶。我希望我在这个问题上错了(我会尽快测试它)。

          事实上,我正在开发一个以 SQL 为中心的轻量级 ORM,我必须在 getPropertyDescriptors 之外添加一些代码,以将其添加到可识别的 setter 以返回它。

          【讨论】:

            【解决方案23】:

            很久以前的回答,但我的两分钱......没关系。我希望这个流畅的界面能被更多地使用。

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

            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】:

              如果可用,最好使用其他语言结构。例如,在 Kotlin 中,您可以使用 withapplylet。如果使用这种方法,您将真正需要从您的 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】:

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

                但是,在我看来,这确实是一个偏好问题,并且链式设置器看起来确实很酷。

                【讨论】:

                  【解决方案26】:

                  我同意所有声称这违反了 JavaBeans 规范的海报。有理由保留这一点,但我也觉得使用这个 Builder 模式(被暗示)有它的位置;只要不是到处都用,应该是可以接受的。对我来说,“It's Place”是调用“build()”方法的终点。

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

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

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

                  【讨论】:

                    【解决方案27】:

                    坏习惯: 二传手 一个吸气剂得到

                    如何显式声明一个方法,为 U 做它

                    setPropertyFromParams(array $hashParamList) { ... }
                    

                    【讨论】:

                    • 不利于自动完成和重构以及样板代码的可读性
                    • 因为:1)您必须记住顺序或从文档中读取它,2)您失去了所有编译时类型安全性,3)您必须转换值(this 和 2)是当然,动态语言中的一个非问题),4)除了在每次调用时检查数组长度之外,您可以有用地扩展它,以及 5)如果您更改任何内容,无论是顺序还是某些值的存在,您都可以检查运行时和编译时间,毫无疑问根本。使用二传手:1)、2)、3):没问题。 4) 添加新方法不会破坏任何东西。 5) 明确的错误信息。
                    猜你喜欢
                    • 2021-05-07
                    • 2014-08-15
                    • 1970-01-01
                    • 1970-01-01
                    • 2023-04-10
                    相关资源
                    最近更新 更多