- 原文鏈接 https://dzone.com/articles/design-patterns-the-builder-pattern
開(kāi)發(fā)模式非常有用,它們?yōu)橐恍┏R?jiàn)的問(wèn)題提供了可靠,高效的解決方案。此外,它們成為開(kāi)發(fā)人員之間通用的語(yǔ)言。
Builder模式是一種創(chuàng)建模式 - 換句話說(shuō),它用于創(chuàng)建和初始化對(duì)象。
問(wèn)題
假設(shè)我們是一個(gè)Java團(tuán)隊(duì),為一家銀行開(kāi)發(fā)軟件。 我們需要一個(gè)類(lèi)來(lái)表示銀行賬戶(hù)。 以下是BankAccount類(lèi)的第一個(gè)版本(請(qǐng)注意,使用double作為貨幣的數(shù)據(jù)類(lèi)型是一個(gè)壞主意 )。
public class BankAccount {
private long accountNumber;
private String owner;
private double balance;
public BankAccount(long accountNumber, String owner, double balance) {
this.accountNumber = accountNumber;
this.owner = owner;
this.balance = balance;
}
//Getters and setters omitted for brevity.
}
非常簡(jiǎn)單 - 我們可以按如下方式使用它。
BankAccount account = new BankAccount(123L, "Bart", 100.00);
很遺憾,我們要不停添加新功能。 一個(gè)新需求是,我們要跟蹤每個(gè)帳戶(hù)的利率,開(kāi)戶(hù)日期,以及可選的開(kāi)設(shè)分行。 這聽(tīng)起來(lái)很容易,所以我們做出了BankAccount類(lèi)的2.0版本。
public class BankAccount {
private long accountNumber;
private String owner;
private String branch;
private double balance;
private double interestRate;
public BankAccount(long accountNumber, String owner, String branch, double balance, double interestRate) {
this.accountNumber = accountNumber;
this.owner = owner;
this.branch = branch;
this.balance = balance;
this.interestRate = interestRate;
}
//Getters and setters omitted for brevity.
}
于是我們使用以下方式來(lái)創(chuàng)建對(duì)象
BankAccount account = new BankAccount(456L, "Marge", "Springfield", 100.00, 2.5);
BankAccount anotherAccount = new BankAccount(789L, "Homer", null, 2.5, 100.00); //Oops!
這段代碼可以編譯通過(guò),但是實(shí)際上是錯(cuò)誤的,Homer的錢(qián)每個(gè)月都會(huì)增加一倍。 提示:密切關(guān)注傳遞給構(gòu)造函數(shù)的參數(shù)的順序。
如果我們有多個(gè)相同類(lèi)型的連續(xù)參數(shù),很容易出現(xiàn)類(lèi)似的錯(cuò)誤, 并且這樣的錯(cuò)誤很難被發(fā)現(xiàn)。 另外,太多的構(gòu)造函數(shù)參數(shù)會(huì)導(dǎo)致代碼變得難以閱讀。 如果我們有10個(gè)不同的參數(shù),根本無(wú)法快速理解每個(gè)參數(shù)的含義。 更糟糕的是,其中一些值可能是可選的,這意味著我們需要?jiǎng)?chuàng)建多個(gè)不同的構(gòu)造函數(shù)來(lái)處理所有可能的組合,否則我們必須將null傳遞給構(gòu)造函數(shù)(丑陋!)。
你可能認(rèn)為我們可以不寫(xiě)構(gòu)造函數(shù)然后通過(guò)setter方法設(shè)置參數(shù)。 但是,這將帶來(lái)另外的問(wèn)題 - 如果開(kāi)發(fā)人員忘記調(diào)用必須要調(diào)用的setter方法怎么辦? 我們最終可能會(huì)得到一個(gè)只是部分初始化的對(duì)象,同樣,這樣的問(wèn)題編譯階段無(wú)法發(fā)現(xiàn),很難被追蹤。
因此,我們需要解決兩個(gè)問(wèn)題:
- 構(gòu)造函數(shù)參數(shù)太多。
- 對(duì)象狀態(tài)不正確。
這是Builder模式發(fā)揮作用的地方。
Builder模式
Builder模式允許我們編寫(xiě)可讀,易懂的代碼來(lái)初始化復(fù)雜的對(duì)象。你可能已經(jīng)在Apache Camel或Hamcrest等工具中看到過(guò)類(lèi)似的用法 。
首先我們需要?jiǎng)?chuàng)建一個(gè)新類(lèi):構(gòu)建器類(lèi),它將包含BankAccount類(lèi)中所有字段。 我們用構(gòu)造器類(lèi)來(lái)配置每個(gè)參數(shù),然后使用這些參數(shù)來(lái)返回BankAccount類(lèi)的對(duì)象。 同時(shí),我們將從BankAccount類(lèi)中刪除公共構(gòu)造函數(shù),并將其替換為私有構(gòu)造函數(shù),以便只能通過(guò)構(gòu)建器創(chuàng)建BankAccount對(duì)象。
如下:
public class BankAccount {
public static class Builder {
private long accountNumber; //This is important, so we'll pass it to the constructor.
private String owner;
private String branch;
private double balance;
private double interestRate;
public Builder(long accountNumber) {
this.accountNumber = accountNumber;
}
public Builder withOwner(String owner){
this.owner = owner;
return this; //By returning the builder each time, we can create a fluent interface.
}
public Builder atBranch(String branch){
this.branch = branch;
return this;
}
public Builder openingBalance(double balance){
this.balance = balance;
return this;
}
public Builder atRate(double interestRate){
this.interestRate = interestRate;
return this;
}
public BankAccount build(){
//Here we create the actual bank account object, which is always in a fully initialised state when it's returned.
BankAccount account = new BankAccount(); //Since the builder is in the BankAccount class, we can invoke its private constructor.
account.accountNumber = this.accountNumber;
account.owner = this.owner;
account.branch = this.branch;
account.balance = this.balance;
account.interestRate = this.interestRate;
return account;
}
}
//Fields omitted for brevity.
private BankAccount() {
//Constructor is now private.
}
//Getters and setters omitted for brevity.
}
我們現(xiàn)在可以創(chuàng)建新帳戶(hù),如下所示。
BankAccount account = new BankAccount.Builder(1234L)
.withOwner("Marge")
.atBranch("Springfield")
.openingBalance(100)
.atRate(2.5)
.build();
BankAccount anotherAccount = new BankAccount.Builder(4567L)
.withOwner("Homer")
.atBranch("Springfield")
.openingBalance(100)
.atRate(2.5)
.build();
這樣的代碼大大提高了可讀性。
總結(jié)
我們通過(guò)一個(gè)示例開(kāi)始,代碼從簡(jiǎn)單開(kāi)始,然后變得越來(lái)越復(fù)雜。 然后,我們使用Builder模式來(lái)解決我們發(fā)現(xiàn)的問(wèn)題。