Java最佳实现Builder模式的方法

15

以下哪种方法是实现建造者模式的更好方法?

1) 使用对象来构建,而不是在建造者中使用所有属性(并在建造者构造函数中创建它):

public class Person {
    private String firstName;
    // other properties ...

    private Person() {}

    // getters ...

    public static class Builder {
        // person object instead of all the person properties
        private Person person;

        public Builder() {
            person = new Person();
        }

        public Builder setFirstName(String firstName) {
            person.firstName = firstName;

            return this;
        }

        // other setters ...

        public Person build() {
            if (null == person.firstName) {
                throw new IllegalStateException("Invalid data.");
            }

            return person;
        }
    }
}

2)在构建器中使用对象的属性来构建对象,而不是直接构建对象(并在build()方法中创建它):

public class Person {
    private String firstName;
    // other properties ...

    private Person() {}

    // getters ...

    public static class Builder {
        // person properties instead of object
        private String firstName;
        // other properties ...

        public Builder() {}

        public Builder setFirstName(String firstName) {
            this.firstName = firstName;

            return this;
        }

        // other setters ...

        public Person build() {
            if (null == this.firstName) {
                throw new IllegalStateException("Invalid data.");
            }

            Person person = new Person();
            person.firstName = firstName;

            return person;
        }
    }
}

我更喜欢第一种方式,因为我认为在构建器中重复大量属性是多余的。第一种方法有什么缺点吗?

提前感谢您,对我的糟糕英语表示抱歉。

4个回答

11

小提示:是的,属性可能会重复,但它们有优势。

详细信息如下:如果您查看此处的详细信息。

Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);

问题在于,由于对象是在多个调用中创建的,因此它可能在构建过程中处于不一致的状态。这也需要额外的工作来确保线程安全。

更好的选择是使用建造者模式。

请注意下面的建造者方法及其对应的构造函数或父级Pizza类——完整代码在链接这里中。

 public static class Builder {

    public Pizza build() {    // Notice this method
      return new Pizza(this);
    }
  }

  private Pizza(Builder builder) {  // Notice this Constructor
    size = builder.size;
    cheese = builder.cheese;
    pepperoni = builder.pepperoni;
    bacon = builder.bacon;
  }

1
+1 是为了展示 Pizza 构造函数,这可能会导致一个不可变对象 - 至少在我开始使用它之前是这样的。 - Maarten Bodewes

3
“生成器”模式被描述在《设计模式》一书中,该书是四人帮(Gang of Four)所著。
引用书中的话:“生成器模式是一种设计模式,允许使用正确的操作序列逐步创建复杂对象。构建由一个仅需要知道要创建的对象类型的指导对象控制。”
如果在构建对象时需要遵循一系列步骤,则选择第二个选项。
在第一个选项中,没有控制正确的操作序列。如果未定义操作序列,则可以选择任何选项。

那么,除非我不必按特定顺序设置属性,否则两种方法都是有效的吗? 从建造者模式的角度来看,第二个选项始终优先考虑,以控制对象创建的方式。 - raxell

1
我认为在您的情况下,无论在哪里创建对象都没有关系。建造者模式在两种情况下的使用方式基本相同,只有性能上的最小差异。
但是,如果对象是不可变的,并且其字段可以通过多个步骤创建,则我肯定会选择第二种方法。例如,您可以检查java.lang.StringBuilder的源代码,看到String对象是在最后一步中创建的。
public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

此外,我更喜欢向导模式。它是建造者模式的扩展,可以保护您免受IllegalStateException的影响。
public class Person {

    private String firstName;
    // other properties ...

    private Person() {}

    // getters ...

    public static class Builder {

        public Builder() {}

        public FirstStep setFirstName(String firstName) {
            return new FirstStep(firstName);
        }

        public static class FirstStep {

            private String firstName;

            private FirstStep(String firstName) {
                this.firstName = firstName;
            }

            public Person build() {
                Person person = new Person();
                person.firstName = firstName;
                return person;
            }
        }
    }
}

什么是“性能最小差异”?哪一个的性能更好? - raxell
理论上,第一种方法应该会更快一些。但是这种事情总是需要通过基准测试来证明。 - Nailgun

0

你可以使用lombok的构建器注释。

例如:

@Data
@Builder
public class Person{
  private String name;
  private String city;
  private String job;
}

使用方法:

Person.builder()
.name("Adam Savage")
.city("San Francisco")
.job("Mythbusters")
.build();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接