深入瞭解 Builder 模式 - frankel

banq發表於2021-09-15

使用 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. 更復雜的是,有些步驟取決於之前的步驟(例如,只有使用混凝土框架才能擁有平屋頂)

相關文章