“只设置一次”的要求感觉有点武断。我很确定您正在寻找的是一个从未初始化状态永久转换为初始化状态的类。毕竟,多次设置对象的id(通过代码重用或其他方式)可能很方便,只要在对象“构建”后不允许更改id即可。
一个相当合理的模式是在单独的字段中跟踪此“已构建”状态:
public final class Example {
private long id;
private boolean isBuilt;
public long getId() {
return id;
}
public void setId(long id) {
if (isBuilt) throw new IllegalArgumentException("already built");
this.id = id;
}
public void build() {
isBuilt = true;
}
}
用法:
Example e = new Example();
// do lots of stuff
e.setId(12345L);
e.build();
// at this point, e is immutable
使用此模式,您可以构造对象,设置其值(尽可能方便的次数),然后调用“immutify”它。build()
与初始方法相比,此模式有几个优点:
- 没有用于表示未初始化字段的幻数值。例如,是与任何其他值一样有效的 ID。
0
long
- 二传手具有一致的行为。在调用之前,它们工作。在 调用 之后,它们会抛出,而不管您传递什么值。(为方便起见,请注意使用未经检查的异常)。
build()
build()
- 该类被标记为 ,否则开发人员可以扩展您的类并重写 setter。
final
但是这种方法有一个相当大的缺点:使用这个类的开发人员在编译时无法知道特定对象是否已初始化。当然,您可以添加一个方法,以便开发人员可以在运行时检查对象是否已初始化,但是在编译时了解此信息会更加方便。为此,您可以使用生成器模式:isBuilt()
public final class Example {
private final long id;
public Example(long id) {
this.id = id;
}
public long getId() {
return id;
}
public static class Builder {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Example build() {
return new Example(id);
}
}
}
用法:
Example.Builder builder = new Example.Builder();
builder.setId(12345L);
Example e = builder.build();
由于以下几个原因,这要好得多:
- 我们使用的是字段,因此编译器和开发人员都知道这些值无法更改。
final
- 对象的初始化形式和未初始化形式的区别通过Java的类型系统进行描述。一旦对象被构建,就根本没有 setter 可以调用它。
- 构建的类的实例保证线程安全。
是的,维护起来有点复杂,但恕我直言,好处大于成本。