ali*_*ind 8 java multithreading inner-classes builder-pattern
我最近学习了Joshua Bloch的构建器模式,用于创建具有许多可选字段的对象.我多年来一直在使用类似的东西,但是直到布洛赫的书向我提出它之前,我从未使用过内部阶级.我喜欢它.
我知道另一个线程可能会在实际构建(使用build())之前更改bulider的配置,这样可能需要重新验证封闭类的构造函数中的所有值.下面是一个构建器类的示例,可以选择性地重新验证其数据.
所以我的问题是这样的:假设这是一个足够强大的设计,当所有值都有默认值时 - 知道这个类是使用默认值的不好选择 - 并且当每次设置尝试都被验证时,是否需要重新检查?虽然它可能不同,但它永远不会无效.那是对的吗?
(虽然这种设计是可管理的,但重新验证的潜在需求肯定会很复杂.老实说,我从来没有多线程,但我不想让我的库无法使用.)
谢谢你的任何建议.
/**
<P><CODE>java ReverifyBuilderInEnclosingCnstrXmpl</CODE></P>
**/
public class ReverifyBuilderInEnclosingCnstrXmpl {
public static final void main(String[] igno_red) {
//Don't reverify
ReverifyBuilderInEnclosingCnstrXmpl rvbdx = new ReverifyBuilderInEnclosingCnstrXmpl.Cfg().
name("Big Bird").age(6).build();
System.out.println(rvbdx.sName);
System.out.println(rvbdx.iAge);
//Do reverify
rvbdx = new ReverifyBuilderInEnclosingCnstrXmpl.Cfg().
reverifyInEnclosing().
name("Big Bird").age(6).build();
}
public final String sName;
public final int iAge;
/**
<P>Create a new <CODE>ReverifyBuilderInEnclosingCnstrXmpl</CODE> with defaults.</P>
**/
public ReverifyBuilderInEnclosingCnstrXmpl() {
//Does not reverify. No need.
this(new ReverifyBuilderInEnclosingCnstrXmpl.Cfg());
}
private ReverifyBuilderInEnclosingCnstrXmpl(ReverifyBuilderInEnclosingCnstrXmpl.Cfg rbdx_c) {
sName = rbdx_c.sName;
iAge = rbdx_c.iAge;
ReverifyBuilderInEnclosingCnstrXmpl.Cfg.zcibValues(rbdx_c, sName, iAge, "constructor");
}
public static class Cfg {
private String sName = null;
private int iAge = -1;
private boolean bReVrfy = false;
public Cfg() {
//Defaults
bReVrfy = false;
name("Broom Hilda");
age(127);
}
//Self-returning configuration...START
//No way to unset.
public Cfg reverifyInEnclosing() {
bReVrfy = true;
return this;
}
public Cfg name(String s_name) {
zcib_name(s_name, "name");
sName = s_name;
return this;
}
public Cfg age(int i_age) {
zcib_age(i_age, "age");
iAge = i_age;
return this;
}
//Self-returning configuration...END
//Validate config...START
public static final void zcibValues(ReverifyBuilderInEnclosingCnstrXmpl.Cfg rbdx_c, String s_name, int i_age, String s_clgFunc) {
try {
if(!rbdx_c.bReVrfy) {
return;
}
} catch(NullPointerException npx) {
throw new NullPointerException("zcibValues: rbdx_c");
}
zcib_name(s_name, s_clgFunc);
zcib_age(i_age, s_clgFunc);
}
public static final void zcib_name(String s_name, String s_clgFunc) {
if(s_name == null || s_name.length() == 0) {
throw new IllegalArgumentException(s_clgFunc + ": s_name (" + s_name + ") is null or empty.");
}
}
public static final void zcib_age(int i_age, String s_clgFunc) {
if(i_age < 0) {
throw new IllegalArgumentException(s_clgFunc + ": i_age (" + i_age + ") is negative.");
}
}
//Validate config...END
public ReverifyBuilderInEnclosingCnstrXmpl build() {
return (new ReverifyBuilderInEnclosingCnstrXmpl(this));
}
}
}
Run Code Online (Sandbox Code Playgroud)
Old*_*eon 11
首先 - 构建器模式本质上不是线程不安全的.我不确定你是如何得出结论的.打算使用构建器的每个线程都将创建自己的Builder对象,以Joshua Bloch的实用和美观的方式填充它并使用它来构造对象.static在该机制中没有任何变量受到影响,除非您自己介绍,否则没有线程不安全.
你对验证的关注 - 在我看来是这样 - 一个粗略的预优化,产生了可怕的设计和可怕的膨胀代码.没有理由因为您知道数据有效而试图避免验证.验证几乎总是微不足道的,通常只需要一些指令.通过使用这些可怕的静态验证方法膨胀类,您可能会添加数千倍的cpu周期来加载这个膨胀的代码,而不是通过避免验证来保存.
将您的设计和臃肿的代码与这个清晰,简洁,明确正确且线程安全的代码进行比较,看看我的意思:
public class Thing {
public final String name;
public final int age;
public Thing() {
this(new Thing.Builder());
}
private Thing(Thing.Builder builder) {
name = builder.name;
age = builder.age;
}
public static class Builder {
private String name = null;
private int age = -1;
public Builder() {
name("Broom Hilda");
age(127);
}
public Builder name(String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Thing.Builder.name (" + name + ") is null or empty.");
}
this.name = name;
return this;
}
public Builder age(int age) {
if (age < 0) {
throw new IllegalArgumentException("Thing.Builder.age (" + age + ") is negative.");
}
this.age = age;
return this;
}
public Thing build() {
return (new Thing(this));
}
}
}
Run Code Online (Sandbox Code Playgroud)
您在架构级别上误解了模式:构造期间的所有数据都绑定到本地线程,而不暴露给任何外部处理程序。调用构建的那一刻,现在完成的参数集将传递给不可变对象,然后该对象首先应在构造函数中验证这些参数的有效性,然后返回最终对象或引发异常。
只要将构建器参数保留在线程本地,就不会引起任何线程问题。如果您违反此规则,则应问自己做的是否正确和/或如何以更细粒度的方式解决它。
因此,如果在您的示例中需要使用来自不同线程的构建器,那么最简单,最安全的方法是创建一个新的构建器实例,而不是静态地执行它。如果您担心性能,ThreadLocal是您的朋友。