深入了解 Builder 模式 - frankel


使用 Builder 模式的一个常见实现是拥有一个流畅的接口,以下为调用者代码:

Person person = new PersonBuilder().withFirstName("John").withLastName("Doe").withTitle(Title.MR).build();


可以通过以下构建器启用此代码段:

public class PersonBuilder {

    private Person person = new Person();
    public PersonBuilder withFirstName(String firstName) {
        person.setFirstName(firstName);
        return this;
    }

    // Other methods along the same model
   
// ...

    public Person build() {
        return person;
    }
}

Builder 的工作完成了:Person实例被很好地封装,只有build()方法最终返回构建的实例。这通常是大多数文章停止的地方,假装已经涵盖了该主题。不幸的是,可能会出现一些需要更深入研究的情况。
假设我们需要一些验证来处理最终Person实例,例如该lastName属性是强制性的。为了提供这一点,我们可以轻松检查该属性是否null在build()方法中并相应地抛出异常。

public Person build() {

    if (lastName == null) {
        throw new IllegalStateException("Last name cannot be null");
    }

    return person;
}

当然,这解决了我们的问题。不幸的是,这种检查发生在运行时,因为调用我们的代码的开发人员会发现(让他们非常懊恼)。为了走向真正的 DSL,我们必须更新我们的设计——很多。我们应该强制执行以下调用者代码:

Person person1 = new PersonBuilder().withFirstName("John").withLastName("Doe").withTitle(Title.MR).build(); // OK
Person person2 = new PersonBuilder().withFirstName(
"John").withTitle(Title.MR).build(); // Doesn't compile

我们必须更新我们的构建器,以便它可以返回自身,或者返回一个缺少build()方法的无效构建器。

转化为以下代码更好:

public class PersonBuilder {

    private Person person = new Person();

    public InvalidPersonBuilder withFirstName(String firstName) {
        person.setFirstName(firstName);
        return new InvalidPersonBuilder(person);
    }

    public ValidPersonBuilder withLastName(String lastName) {
        person.setLastName(lastName);
        return new ValidPersonBuilder(person);
    }

    // Other methods, but NO build() methods
}

public class InvalidPersonBuilder {

    private Person person;

    public InvalidPersonBuilder(Person person) {
        this.person = person;
    }

    public InvalidPersonBuilder withFirstName(String firstName) {
        person.setFirstName(firstName);
        return this;
    }

    public ValidPersonBuilder withLastName(String lastName) {
        person.setLastName(lastName);
        return new ValidPersonBuilder(person);
    }

   
// Other methods, but NO build() methods
}

public class ValidPersonBuilder {

    private Person person;

    public ValidPersonBuilder(Person person) {
        this.person = person;
    }

    public ValidPersonBuilder withFirstName(String firstName) {
        person.setFirstName(firstName);
        return this;
    }

   
// Other methods

   
// Look, ma! I can build
    public Person build() {
        return person;
    }
}


这是一个巨大的改进,因为现在开发人员可以在编译时知道他们构建的对象是无效的。
下一步是想象更复杂的用例:

  1. 必须按特定顺序调用构建器方法。例如,房子应该有地基、框架和屋顶。建造框架需要建造地基,就像建造屋顶需要框架一样。
  2. 更复杂的是,有些步骤取决于之前的步骤(例如,只有使用混凝土框架才能拥有平屋顶)