简体   繁体   English

使Bloch的构建器模式成为线程安全的:如果NEVER无效,则需要重新检查封装构造函数吗?

[英]Making Bloch's builder pattern thread-safe: Rechecking necessary in enclosing constructor if NEVER invalid?

I have recently learned Joshua Bloch's builder pattern for creating objects with many optional fields. 我最近学习了Joshua Bloch的构建器模式,用于创建具有许多可选字段的对象。 I've been using something like it for years, but never used an inner-class until Bloch's book suggested it to me. 我多年来一直在使用类似的东西,但是直到布洛赫的书向我提出它之前,我从未使用过内部阶级。 I love it. 我喜欢它。

I understand that another thread may alter the bulider's configuration, before it's actually built (with build() ), such that it may be necessary to re-validate all values in the constructor of the enclosing class. 我知道另一个线程可能会在实际构建之前改变bulider的配置(使用build() ),这样可能需要重新验证封闭类的构造函数中的所有值。 Below is an example of a builder class that optionally reverifies its data. 下面是一个构建器类的示例,可以选择性地重新验证其数据。

So my question is this: Assuming this is a robust enough design, when there are defaults for all values--knowing this class is a poor choice for using defaults--and when every set-attempt is validated, is this re-check necessary? 所以我的问题是这样的:假设这是一个足够强大的设计,当所有值都有默认值时 - 知道这个类是使用默认值的不好选择 - 并且当每次设置尝试都被验证时,这是否需要重新检查? Although it may be different , it would never be invalid . 虽然它可能不同 ,但它永远不会无效 Is that correct? 那是对的吗?

(Although this design is manageable, it is certainly complicated by the potential need for re-verification. And, honestly, I never multi-thread, but I don't want to make my library unusable by people that do.) (虽然这种设计是可管理的,但重新验证的潜在需求肯定会很复杂。老实说,我从来没有多线程,但我不想让我的库无法使用。)

Thank you for any advice. 谢谢你的任何建议。

/**
   <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));
      }
   }
}

Firstly - the builder pattern is not inherently thread unsafe. 首先 - 构建器模式本质上不是线程不安全的。 I am not sure how you are concluding that it is. 我不确定你是如何得出结论的。 Each thread that intends to use the builder will create its own Builder object, populate it in Joshua Bloch's pragmatic and beautiful way and use it to construct the object. 打算使用构建器的每个线程都将创建自己的Builder对象,以Joshua Bloch的实用且美观的方式填充它并使用它来构造对象。 There are no static variables being affected anywhere in that mechanism, there is no thread unsafety unless you introduce it yourself. 在该机制中没有任何static变量受到影响,除非您自己介绍,否则没有线程不安全。

Your concern about validation is - in my humble opinion - a gross pre-optimisation that produces hideously contrived and horribly bloated code. 你对验证的关注 - 在我看来是这样 - 一个粗略的预优化,产生了可怕的设计和可怕的膨胀代码。 There is no reason to try to avoid validation just because you know the data is valid . 没有理由因为您知道数据有效而试图避免验证 Validation is almost always trivial and often takes little more that a few instructions. 验证几乎总是微不足道的,通常只需要一些指令。 By bloating the class with these horrible static validation methods you are probably adding thousands of times more cpu cycles just to load this bloated code than you are saving by avoiding the validation. 通过使用这些可怕的静态验证方法膨胀类,您可能会添加数千倍的cpu周期来加载这个膨胀的代码,而不是通过避免验证来保存。

Compare your contrived and bloated code with this lucid, succinct and patently correct and thread safe code and see what I mean: 将您的设计和臃肿的代码与这个清晰,简洁,明确正确且线程安全的代码进行比较,看看我的意思:

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));
        }
    }
}

You are misunderstanding the pattern on an architectural level: all data during construction is tied to the local thread and not to be exposed to any external handler. 您在架构级别上误解了模式:构造期间的所有数据都绑定到本地线程,而不是暴露给任何外部处理程序。 The very moment build is called, the now finalized set of parameters is passed to an immutable object, which then first should verify the validity of those parameters in the constructor, then either return the final object or throw an exception. 在调用构建的那一刻,将现在最终确定的参数集传递给不可变对象, 然后首先应该验证构造函数中这些参数的有效性,然后返回最终对象或抛出异常。

As long as you keep the builder parameters thread-local, you cannot cause any threading-issues. 只要将构建器参数保持在线程本地,就不会导致任何线程问题。 If you violate this rule, you should ask yourself if what you are doing is correct and/or how you could solve it in a more fine-grained way. 如果违反此规则,您应该问问自己,您所做的事情是否正确和/或您如何以更细粒度的方式解决它。

So if you in your example need to use the builder from different threads, the simplest and safest way is to create a new builder instance instead of doing it statically. 因此,如果您的示例中需要使用来自不同线程的构建器,则最简单和最安全的方法是创建新的构建器实例,而不是静态地执行它。 If you worry about performance, ThreadLocal is your friend. 如果您担心性能, ThreadLocal就是您的朋友。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM