有没有类似 "build-once" 生成器的东西?

Is there something like a "build-once" builder?

我们的框架提供了某种 "log" 对象;它实际上代表了我们遗留 C/C++ 代码中的遗留结构。今天,我开始编写一个帮助程序 class,它使用构建器模式来允许使用更现代的方法来创建此类日志对象。

但是有一题我不确定正确答案是什么:

基本上这些日志对象有一些属性是"required";因此,按照构建器模式,您必须将这些参数指定为我的新构建器 class 的 ctor 的参数。然后构建器有一些方法来配置 "optional attributes";当然还有一个方法"build()",它会发出一个日志对象,其中包含收集到的值的摘要。

但现在我想知道:我应该关心人们是否会重复使用同一个构建器对象吗?那么,我是否应该使用 "reset()" 方法来清除所有可选参数?或防止 build() 被多次调用? 或者最好不要担心?

无需重新设置,只需继续允许使用相同的构建器对象生成新对象即可。开发人员可以创建一个新的构建器,以避免重复使用之前设置的内容。此外,如果您想了解它应该如何工作,Twitter4j 也有类似的东西(并且是开源的)(参见 http://twitter4j.org/en/configuration.html):

ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true)
  .setOAuthConsumerKey("*********************")
  .setOAuthConsumerSecret("******************************************")
  .setOAuthAccessToken("**************************************************")
  .setOAuthAccessTokenSecret("******************************************");
TwitterFactory tf = new TwitterFactory(cb.build());
Twitter twitter = tf.getInstance();

我发现在这种情况下有用的是,当构建器被多次重复使用并且可能存在一些剩余数据重叠时,在构建器 class 中包含您想要的未初始化对象构建,并将初始化责任留给构建器实例。这样构建的对象就不会关心(或知道)以前构建会话遗留下来的垃圾,如果它需要相同的可选数据,构建器也知道重新初始化该数据。

should I care if people would be re-using the same builder object?

好吧,你想观察副作用,但我想这就是你问的原因,因为你知道 people/code 会在生成器中留下垃圾。

So, should I have a "reset()" method to clear all optional arguments;

我认为如果你传递一个构建器,清理的责任应该在消费者身上(函数或 class 将构建器作为输入)。这样我们就不会造成副作用。示例:

public class SomeClass {
    public SomeClass(Log.Builder builder){
       builder.tag("SomeClass"); //bad, we caused a side effect
                                 //someone class forgets to set the tag
                                 //later, then they get this tag
       log = builder.build();
    }
}

一个解决方案是提供一个复制构造函数:

public class SomeClass {
    public SomeClass(Log.Builder builder){
       Log.Builder localBuilder = new Log.Builder(builder); //copy constructor being used.
       localBuilder.tag("SomeClass"); //only applies to the local builder
       log = localBuilder.build();
    } //builder is as it was before the call, no side effect
}

你甚至可以用一个副本调用 SomeClass,这样你就不需要关心它是否被实现为没有副作用:

Log.Builder defaults = new Log.Builder(...whatever...).tag("notag");
new SomeClass(new Log.Builder(defaults));
new SomeClass2(new Log.Builder(defaults));

要进一步锁定它,您可以有一个接口 Log.ReadOnlyBuilder,它没有设置器,实际上只适用于构建或创建副本:

public class Log {
    public interface ReadOnlyBuilder {
        Builder copy();
        Log build();
    }

    public static class Builder implements ReadOnlyBuilder {
        Builder tag(String tag){
          ...
        }
    }
}

public class SomeClass {
    public SomeClass(Log.ReadOnlyBuilder builder){
       log = builder.copy().tag("SomeClass").build();
    } //builder is as it was before the call, no side effect
}

我喜欢这个,因为责任回到了消费者身上,不造成副作用,但它是通过类型和接口强制执行的。另外,如果他们不需要设置任何字段,他们可以只使用只读构建器,我们不必复制以防万一。

P.S。我不认为 optional 和 required 是你想要决定重置什么和设置什么的划分点。我认为复制构造函数应该只将构建器中的每个字段复制到新的构建器中。但是你可以自由地发展copymethod/constructor。