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;
}
答案 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
实例作为构造函数参数,但构建器应该让实例拉出所有需要的状态,释放构建器实例并保留对相关数据的最终引用。