8

我最近学习了Joshua Bloch 的构建器模式,用于创建具有许多可选字段的对象。多年来我一直在使用类似的东西,但在 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));
      }
   }
}
4

2 回答 2

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));
        }
    }
}
于 2014-01-06T00:08:24.110 回答
5

您误解了架构级别的模式:构建期间的所有数据都绑定到本地线程,而不是暴露给任何外部处理程序。在调用 build 的那一刻,现在完成的参数集被传递给一个不可变对象,然后它首先应该在构造函数中验证这些参数的有效性,然后返回最终对象或抛出异常。

只要您将构建器参数保持在线程本地,就不会导致任何线程问题。如果你违反了这条规则,你应该问问自己你所做的是否正确和/或如何以更细粒度的方式解决它。

因此,如果您在示例中需要使用来自不同线程的构建器,最简单和最安全的方法是创建一个新的构建器实例,而不是静态地执行它。如果您担心性能,ThreadLocal是您的朋友。

于 2014-01-06T11:14:32.797 回答