这是不可变类和Builder模式的有效Java实现吗?

时间:2010-09-13 19:01:06

标签: java immutability builder-pattern

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

3 个答案:

答案 0 :(得分:6)

通常,从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);
}

答案 1 :(得分:3)

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

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

您可能想要考虑的潜在变体:如果您使构建器对象本身不可变,则无需进行防御性克隆。这可能是一个全面的胜利,特别是如果你比更改构建器更频繁地构建对象。

答案 2 :(得分:3)

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

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

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