使用构建器模式最多设置一次值

big*_*gfy 7 java builder-pattern

在使用构建器模式时,Java中是否存在标准实践,以确保成员变量最多设置一次.我需要确保setter被调用0或1次但从不更多.我想抛出RuntimeException一些类型,但我担心同步问题以及该领域的最佳实践.

dim*_*414 6

没有什么是用,如果用户调用一个方法以非法的方式像你描述抛出一个异常,但它并不十分优雅.构建器模式背后的想法是让用户编写流畅,可读的对象定义,编译时安全性是其中很重要的一部分.如果用户无法确信构建器即使在编译时也会成功,那么您将引入用户现在需要理解和解释的额外复杂性.

有几种方法可以实现您所描述的内容,让我们探索它们:

  1. 让用户做他们想做的事

    关于构建器的一个好处是它们可以让你从同一个构建器构造多个不同的对象:

    List<Person> jonesFamily = new ArrayList<>();
    Person.Builder builder = new Person.Builder().setLastName("Jones");
    
    for(String firstName : jonesFamilyFirstNames) {
      family.add(builder.setFirstName(firstName).build());
    }
    
    Run Code Online (Sandbox Code Playgroud)

    我认为你有充分的理由禁止这种行为,但如果我没有提出这个有用的伎俩,我就会失职.也许你不需要首先限制它.

  2. 提出例外

    你建议提出一个例外,这肯定会奏效.就像我说的那样,我不认为这是最优雅的解决方案,但这里有一个实现(使用Guava前提条件,为了额外的可读性):

    public class Builder {
      private Object optionalObj = null;
      // ...
    
      public Builder setObject(Object setOnce) {
        checkState(optionalObj == null, "Don't call setObject() more than once");
        optionalObj = setOnce;
      }
      // ...
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这就提出了一个IllegalStateException,所以你可以打电话throw new IllegalStateException(),如果你不使用番石榴(你应该是... :)).假设您没有在线程之间传递构建器对象,则应该没有同步问题.如果你是,你应该进一步思考为什么你需要在不同的线程中使用相同的构建器 - 这几乎肯定是反模式.

  3. 根本不提供方法

    这是阻止用户调用您不希望它们使用的方法的最干净,最清晰的方法 - 首先不要提供它.相反,覆盖构建器的构造函数或build()方法,以便它们可以选择在那时传递值,但不能在其他时间传递.这样你就可以清楚地保证每个构造对象最多可以设置一次.

    public class Builder {
      // ...
    
      public Obj build() { ... }
      public Obj build(Object onceOnly) { ... }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 使用不同的类型来公开某些方法

    我实际上并没有这样做,它可能比它的价值更令人困惑(特别是,你可能需要在方法中使用自我限制泛型Builder),但在我写作的时候会想到它并且可以对某些用例非常明确.在构建器的子类中使用受限制的方法,并且该方法返回父类型,从而阻止调用者重新调用该方法.如果没有意义,一个例子可能会有所帮助:

    public class Builder {
      // contains regular builder methods
    }
    
    public class UnsetBuilder extends Builder {
      public Builder setValue(Object obj) { ... }
    }
    
    // the builder constructor actually returns an UnsetBuilder
    public static UnsetBuilder builder() { ... }
    
    Run Code Online (Sandbox Code Playgroud)

    然后我们可以调用类似的东西:

    builder().setValue("A").build();
    
    Run Code Online (Sandbox Code Playgroud)

    但是如果我们试图调用,我们会得到一个编译时错误:

    builder().setValue("A").setValue("B").build();
    
    Run Code Online (Sandbox Code Playgroud)

    因为缺少方法的setValue()返回Builder,setValue()因此阻止了第二种情况.要完全正确(如果用户将Builder背面投射到UnsetBuilder?)会很困难,但是只要付出一些努力就可以完成您正在寻找的东西.