9

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

4

4 回答 4

7

如果用户以您描述的非法方式调用方法,则引发异常并没有,但这并不是非常优雅。构建器模式背后的想法是让用户编写流畅、可读的对象定义,而编译时安全是其中的重要组成部分。如果用户无法确信构建器即使编译也会成功,那么您将引入用户现在需要理解和考虑的额外复杂性。

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

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

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

  3. 根本不提供方法

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

    public class Builder {
      // ...
    
      public Obj build() { ... }
      public Obj build(Object onceOnly) { ... }
    }
    
  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() { ... }
    

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

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

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

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

    因为setValue()returnBuilder缺少setValue()方法,因此防止了第二种情况。这将很难完全正确(如果用户将Builder背面投射到 aUnsetBuilder怎么办?)但是通过一些努力可以完成您正在寻找的事情。

于 2014-08-26T05:19:42.227 回答
3
Object objectToSet;

boolean isObjectSet = false;

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

}

这是标准做法。

于 2014-08-26T03:56:25.647 回答
0

您可以将变量定义为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方法可能会导致巨大处理中的性能不稳定。

于 2014-08-26T04:03:41.327 回答
0

您可以使用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真正的不可变。

于 2014-08-26T04:33:05.353 回答