这是不可变类和 Builder 模式的有效 Java 实现吗?
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;
}
The Builder implements Cloneable and overrides clone() and instead of copying every field of the builder, the immutable class keeps a private clone of the builder. This makes it easy to return a new builder and create slightly modified copies of an immutable instance.
This way I can go
MyImmutable i1 = new MyImmutable.Builder().foo(1).bar(2).build();
MyImmutable i2 = i1.builder().foo(3).build();
The Cloneable interface is said to be somewhat broken, but does any of this violate good java coding practice, are there any problems with this construct?
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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您的实现与 Josh Bloch 的《Effective Java 第二版》中详细介绍的实现类似。
争论的焦点之一是您的
build()
方法。如果单个构建器创建一个不可变实例,考虑到它的工作已经完成,允许再次使用该构建器是否公平?这里需要注意的是,即使您正在创建一个不可变的对象,构建器的可变性也可能会导致一些相当“令人惊讶”的错误。为了纠正这个问题,可能建议构建方法应该创建实例,然后使构建器无法再次构建对象,从而需要一个新的构建器。尽管样板文件可能看起来很乏味,但以后获得的好处会超过当前所需的努力。然后,实现类可以接收
Builder
实例作为构造函数参数,但构建器随后应该让实例提取所有需要的状态,释放构建器实例并保留对相关数据的最终引用。Your implementation is similar to the implementation detailed in Josh Bloch's Effective Java 2nd Edition.
One point of contention would be your
build()
method. If a single builder creates an immutable instance, would it be fair to allow the builder to be used again, considering that it's work has been done? The caution here is that even though you are creating an immutable object, the mutability of your builder may result in a few rather "surprising" bugs.To correct this, it may be suggested that the build method should create the instance and then make the builder incapable of building the object again, requiring a new builder. Although the boiler-plate may seem tedious, the benefits to be reaped later outweigh the required effort at present. The implementation class can then receive a
Builder
instance as a constructor parameter, but the builder should then let the instance pull out all it's needed state, releasing the builder instance and preserving final references to the data in question.我以前没有见过这种方法,但看起来效果很好。
基本上,它使构建器模式的实现相对简单,但代价是运行时开销稍高(额外的对象+克隆操作+访问器函数中可能会或可能不会编译出来的间接级别)。
您可能需要考虑的一个潜在变化:如果您使构建器对象本身不可变,则无需防御性地克隆它们。这可能是一个全面的胜利,特别是如果您构建对象的频率比更改构建器的频率高得多。
I've not seen this approach before, but looks like it would work fine.
Basically it makes the builder pattern relatively simple to implement, at the expense of slightly higher runtime overhead (extra objects + cloning operations + a level of indirection in accessor functions that may or may not get compiled out).
A potential variation you might want to consider: if you make the builder objects themselves immutable, you won't need to defensively clone them. This could be an overall win, especially if you build objects a lot more frequently than you change the builders.
通常,从生成器构造的类没有任何生成器的专业知识。也就是说 Immutable 将有一个构造函数来提供 foo 和 bar 的值:
该构建器将是一个单独的类:
如果您愿意,您可以向 MyImmutable 构建器添加一个静态方法,以从现有的 MyImmutable 实例开始:
Generally the class constructed from the Builder doesn't have any specialized knowledge of the builder. That is Immutable would have a constructor to supply the value for foo and bar:
The builder would be a separate class:
If you wanted, you could add a static method to MyImmutable builder to start from an existing MyImmutable instance: