Java 在正确的位置使用正确的生成器

Java 在正确的位置使用正确的生成器,java,multithreading,design-patterns,builder,qa,Java,Multithreading,Design Patterns,Builder,Qa,我对建筑模式非常感兴趣,我经常使用它,但我不确定我制作的建筑是否足够好,而且我对我可以使用它们的所有环境都有疑问。 以下是我如何创建生成器的示例: public class Person { private String name; private String secondName; private int age; public static class Builder { private boolean isBuilt;

我对建筑模式非常感兴趣,我经常使用它,但我不确定我制作的建筑是否足够好,而且我对我可以使用它们的所有环境都有疑问。 以下是我如何创建生成器的示例:

public class Person {

    private String name;
    private String secondName;
    private int age;

    public static class Builder {
        private boolean isBuilt;
        private Person person = new Person();

        private void check() {
            if (isBuilt) {
                throw new IllegalStateException(
                        "The object cannot be modified after built");
            }
        }

        public Builder withName(String name) {
            check();
            person.name = name;
            return this;
        }

        public Builder withSecondName(String secondName) {
            check();
            person.secondName = secondName;
            return this;
        }

        public Builder withAge(int age) {
            check();
            person.age = age;
            return this;
        }

        public Person build() {
            check();
            isBuilt = true;
            return person;
        }
    }

    @Override
    public String toString() {
        return "Name: " + name + "\nSecond name:" + secondName + "\nAge:" + age;
    }
}
只是一个快速使用示例:

Person person = new Person.Builder()
        .withName("John")
        .withSecondName("Smith")
        .withAge(50)
        .build();
        System.out.println(person);
以下是我的一些疑问:

  • 你认为它真的是一成不变的吗?如果不是,我如何改进它
  • 关于线程安全。这可能是我主要的怀疑。这真的是线程安全的吗? 我在互联网上看到过这样的例子:类级变量必须是final,并通过构造函数传递。我还看到一个例子,其中变量被声明为volatile。你觉得怎么样
  • 您认为这个构建器在可以使用的场景方面会有任何限制吗?我的意思是,它是否适合在EJB、JSF支持bean、MDB中调用,或者成为JPA实体
你认为它真的是不变的吗?[…]这真的是线程安全的吗

代码的任何部分都是不可变的。这也可能会妨碍线程安全;也就是说,以二进制方式声明一个类是线程安全的还是非线程安全的确实很困难。我也不明白为什么你会在一开始就在线程之间共享构建器实例,但是你的代码示例的简单性可能会误导我

为了更容易实现线程安全,您的
Builder
s本身应该是不可变的。这意味着每个
withXXX()
方法都应该返回一个表示新状态的新生成器。(可能有更聪明的方法可以做到这一点,但这将是一种直截了当的方法。)

不过我要重申一点:我不确定让构建器线程安全是绝对必要的——大多数情况下,它们都是生命周期很短且可视范围很广的对象。您是否希望使它们不可变取决于用例,您可能希望存储部分填充的构建器,但这也有点罕见。(主观上,名称以
with
开头的方法与名称以
set
开头的方法相比,不就地修改对象似乎更直观)

您认为这个构建器在可以使用的场景方面会有任何限制吗


这通常是无法回答的,但是如果您确实使您的
Person
对象不可变,因此只能由构建器构造,那么它们将无法作为JPA实体使用,我猜也是作为JSF支持bean使用的。Java框架为您创建/管理某些对象的频率高于预期的JavaBeans,这意味着可以通过反射调用无参数构造函数和属性设置器来创建这些对象。

Builder
应该具有与
Person
相同的字段,而不是其中的另一个
Person
,并且
Person
中的所有字段都应该是
final
。我不喜欢这样综合问题。你也应该考虑删除一些主观的问题,比如“你喜欢我的代码”或者“我怎么命名事物”。“LouisWasserman看看这个链接,我发现也有人这样做。我想删除重复,但我不知道我是否做得很好:他建议使用
volatile
关键字,但我不确定这在线程安全方面是否完全有效。@millimoose此模式有很多感兴趣的话题供讨论,我希望能得到尽可能多的反馈。关于你提到的这个问题的主观主义,我认为这很好,我主要关心的是如何更好地实现这个模式。
volatile
也不起作用。不变性是一个夸大其词的问题——不安全的出版物很少被使用。谢谢你的回答。关于JPA和JSF的讨论非常有趣。我认为JPA提供的替代方案是
CDI
,它允许注入POJO。你觉得怎么样?您说过可能有更聪明的方法来实现它,如果您能用一个小例子更新答案,我将不胜感激。@sfrj更聪明的方法包括返回一个更合作的
XxxBuilder
,通过它们的类型指示哪些操作是有效的。从本质上讲,您可以使用构建器对表示对象构造的状态机进行建模,其中
withXxx()
方法是状态转换,而
build()
方法将导致终端状态。我不知道它是否适用于构建您的示例中的数据结构。(或者构建器模式对数据结构是否实用。)@sfrj“我想JPA提供的替代方案是CDI,它允许注入POJO。你对此有何看法?”-我想你是在胡乱使用流行语,试图让我向你解释Java EE技术。但无论如何。我不明白JPA是如何以任何方式提供CDI的,我强烈怀疑您是否可以让CDI创建JPA实体,所以我不确定您为什么要将两者连接起来。CDI支持构造函数注入,所以它可以让您管理不可变的支持bean,但它们仍然不会由您的构建者构建,所以这一点是没有意义的。现在您提到了数据结构,我更困惑了:)我认为这个主题会将这个问题的范围扩大太多。在dzone中,我找到了一个关于如何实现不可变性的很好的解释:但我唯一不喜欢的是复制。我希望避免重复代码。很抱歉,CDI容器提供了错误的CDI。是的,它允许使用
@inject
注释将POJO注入JSF支持bean等位置。