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

时间:2014-08-26 03:48:52

标签: java builder-pattern

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

4 个答案:

答案 0 :(得分: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());
    }
    

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

  2. 提出异常

    您建议提出异常,这肯定会奏效。就像我说的那样,我不认为这是最优雅的解决方案,但这是一个实现(使用GuavaPreconditions,以获得额外的可读性):

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

    这会引发IllegalStateException,所以如果你不使用番石榴,你可以调用throw new IllegalStateException()(你应该......)。假设您没有在线程之间传递构建器对象,则应该没有同步问题。如果你是,你应该进一步考虑为什么你需要在不同的线程中使用相同的构建器 - 这几乎肯定是一种反模式。

  3. 根本不提供该方法

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

    public class Builder {
      // ...
    
      public Obj build() { ... }
      public Obj build(Object onceOnly) { ... }
    }
    
  4. 使用不同类型公开某些方法

    我实际上并没有这样做,它可能比它的价值更令人困惑(特别是,您可能需要使用self-bounding genericBuilder中的方法,但它在我写作的过程中浮现在脑海中,对于某些用例可能非常明确。在构建器的子类中使用受限制的方法,并且该方法返回父类型,从而阻止调用者重新调用该方法。如果没有意义,一个例子可能会有所帮助:

    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() { ... }
    

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

    builder().setValue("A").build();
    

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

    builder().setValue("A").setValue("B").build();
    

    因为setValue()返回Builder,缺少setValue()方法,因此阻止了第二种情况。要完全正确(如果用户将Builder转换回UnsetBuilder会怎么样?)会很困难但是只要付出一些努力就可以做你正在寻找的事情。

答案 1 :(得分:3)

Object objectToSet;

boolean isObjectSet = false;

void setObject(Object object) throws RuntimeException {
    if(!isObjectSet) {
        objectToSet=object;
        isObjectSet=true;
    } else {
       throw new RuntimeException();
    }

}

这是标准做法。

答案 2 :(得分:1)

您可以将变量定义为final

final MyClass object;

编辑:如果多次设置,则会出现编译时错误。


如其他人所述,您可以在setter中检查变量状态,为了线程安全,将setter方法声明为synchronized

public synchronized void setMyObject(Object object)
{
    if (this.myObject == null)
    {
        this.myObject = object;
    } else {
        throw new RuntimeException();
    }
}

甚至在构造函数或其他任何地方使用setter方法。

注意:使用synchronized方法可能会导致巨大处理中的性能不稳定。

答案 3 :(得分:1)

您可以使用 Effective Java 中概述的Builder模式。这并不完全符合您的规格,但我认为它应该满足您的需求。让我知道。

class Foo {
    private final Object objectToSet;

    private Foo(Foo.Builder builder) { 
        objectToSet = builder.getObjectToSet();
    }

    public static class Builder {
        private Object objectToSet; 

        public Builder() { } 

        private Object getObjectToSet() {
            return objectToSet;
        }

        public Builder objectToSet(Object objectToSet) { 
            this.objectToSet = objectToSet; 
            return this; 
        }

        public Foo build() { 
            return new Foo(this);
        }
    }
}

然后:

Object someObject = 10;
Foo foo = new Foo.Builder()
    .objectToSet(someObject)
    .build(); 
// A Foo's `objectToSet` can only be set once

这允许您定义一个类(此案例Foo),该类具有只能设置一次的属性。作为奖励,您可以使Foo真正不可变。