9

Builder 实现了 Cloneable 并覆盖了 clone() ,而不是复制构建器的每个字段,不可变类保留了构建器的私有克隆。这使得返回一个新的构建器和创建一个不可变实例的稍微修改的副本变得很容易。

这样我就可以走了

MyImmutable i1 = new MyImmutable.Builder().foo(1).bar(2).build();
MyImmutable i2 = i1.builder().foo(3).build();

据说 Cloneable 接口有些损坏,但是这是否违反了良好的 java 编码习惯,这个结构有什么问题吗?

final class MyImmutable { 
  public int foo() { return builder.foo; }
  public int bar() { return builder.bar; }
  public Builder builder() { return builder.clone(); }
  public static final class Builder implements Cloneable {
    public Builder foo(int val) { foo = val; return this; }
    public Builder bar(int val) { bar = val; return this; }
    public MyImmutable build() { return new MyImmutable(this.clone()); }
    private int foo = 0;
    private int bar = 0;
    @Override public Builder clone() { try { return (Builder)super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); } }
  }
  private MyImmutable(Builder builder) { this.builder = builder; }
  private final Builder builder;
}
4

3 回答 3

6

通常,从 Builder 构造的类没有任何关于 builder 的专业知识。也就是说,Immutable 将有一个构造函数来提供 foo 和 bar 的值:

public final class MyImmutable {
  public final int foo;
  public final int bar;
  public MyImmutable(int foo, int bar) {
    this.foo = foo;
    this.bar = bar;
  }
}

构建器将是一个单独的类:

public class MyImmutableBuilder {
  private int foo;
  private int bar;
  public MyImmutableBuilder foo(int val) { foo = val; return this; }
  public MyImmutableBuilder bar(int val) { bar = val; return this; }
  public MyImmutable build() { return new MyImmutable(foo, bar); }
}

如果您愿意,可以向 MyImmutable 构建器添加一个静态方法,以从现有的 MyImmutable 实例开始:

public static MyImmutableBuilder basedOn(MyImmutable instance) {
  return new MyImmutableBuilder().foo(instance.foo).bar(instance.bar);
}
于 2010-09-13T19:20:25.040 回答
3

我以前没有见过这种方法,但看起来它可以正常工作。

基本上,它使构建器模式的实现相对简单,但代价是运行时开销稍高(额外的对象 + 克隆操作 + 访问器函数中可能会或可能不会被编译出来的间接级别)。

您可能需要考虑的一种潜在变化:如果您使构建器对象本身不可变,则无需防御性地克隆它们。这可能是一个整体的胜利,特别是如果您构建对象的频率比更改构建器的频率高得多。

于 2010-09-13T19:18:07.660 回答
3

您的实现类似于 Josh Bloch 的 Effective Java 2nd Edition 中详述的实现。

一个争论点是你的build()方法。如果单个构建器创建了一个不可变的实例,考虑到它的工作已经完成,允许再次使用该构建器是否公平?这里需要注意的是,即使您正在创建一个不可变对象,构建器的可变性也可能会导致一些相当“令人惊讶”的错误。

为了纠正这个问题,建议构建方法应该创建实例,然后使构建器无法再次构建对象,需要一个新的构建器。虽然样板文件可能看起来很乏味,但后来获得的好处超过了目前所需的努力。然后实现类可以接收一个Builder实例作为构造函数参数,但是构建器应该让实例拉出所有它需要的状态,释放构建器实例并保留对相关数据的最终引用。

于 2010-09-13T20:15:10.593 回答