使用 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; } }
|
这是一个巨大的改进,因为现在开发人员可以在编译时知道他们构建的对象是无效的。
下一步是想象更复杂的用例:
- 必须按特定顺序调用构建器方法。例如,房子应该有地基、框架和屋顶。建造框架需要建造地基,就像建造屋顶需要框架一样。
- 更复杂的是,有些步骤取决于之前的步骤(例如,只有使用混凝土框架才能拥有平屋顶)