考慮這樣一個場景栅迄,假如有一個類(****User****)站故,里面有很多屬性,并且你希望這些類的屬性都是不可變的(final)毅舆,就像下面的代碼:
public class User {
private final String firstName; // 必傳參數(shù)
private final String lastName; // 必傳參數(shù)
private final int age; // 可選參數(shù)
private final String phone; // 可選參數(shù)
private final String address; // 可選參數(shù)
}
在這個類中西篓,有些參數(shù)是必要的愈腾,而有些參數(shù)是非必要的。就好比在注冊用戶時岂津,用戶的姓和名是必填的顶滩,而年齡、手機(jī)號和家庭地址等是非必需的寸爆。那么問題就來了礁鲁,如何創(chuàng)建這個類的對象呢?
一種可行的方案就是實(shí)用構(gòu)造方法赁豆。第一個構(gòu)造方法只包含兩個必需的參數(shù)仅醇,第二個構(gòu)造方法中,增加一個可選參數(shù)魔种,第三個構(gòu)造方法中再增加一個可選參數(shù)析二,依次類推,直到構(gòu)造方法中包含了所有的參數(shù)节预。
public User(String firstName, String lastName) {
this(firstName, lastName, 0);
}
public User(String firstName, String lastName, int age) {
this(firstName, lastName, age, "");
}
public User(String firstName, String lastName, int age, String phone) {
this(firstName, lastName, age, phone, "");
}
public User(String firstName, String lastName, int age, String phone, String address) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.phone = phone;
this.address = address;
}
這樣做的好處只有一個:可以成功運(yùn)行叶摄。但是弊端很明顯:
- 參數(shù)較少的時候問題還不大,一旦參數(shù)多了安拟,代碼可讀性就很差蛤吓,并且難以維護(hù)。
- 對調(diào)用者來說也很麻煩糠赦。如果我只想多傳一個address參數(shù)会傲,還必需給age、phone設(shè)置默認(rèn)值拙泽。而且調(diào)用者還會有這樣的困惑:我怎么知道第四個String類型的參數(shù)該傳address還是phone淌山?
第二種解決辦法就出現(xiàn)了,我們同樣可以根據(jù)JavaBean的習(xí)慣顾瞻,設(shè)置一個空參數(shù)的構(gòu)造方法泼疑,然后為每一個屬性設(shè)置setters和getters方法。就像下面一樣:
public class User {
private String firstName; // 必傳參數(shù)
private String lastName; // 必傳參數(shù)
private int age; // 可選參數(shù)
private String phone; // 可選參數(shù)
private String address; // 可選參數(shù)
public User() {
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
}
這種方法看起來可讀性不錯荷荤,而且易于維護(hù)退渗。作為調(diào)用者,創(chuàng)建一個空的對象梅猿,然后只需傳入我感興趣的參數(shù)氓辣。那么缺點(diǎn)呢?也有兩點(diǎn):
- 對象會產(chǎn)生不一致的狀態(tài)袱蚓。當(dāng)你想要傳入5個參數(shù)的時候,你必需將所有的setXX方法調(diào)用完成之后才行几蜻。然而一部分的調(diào)用者看到了這個對象后喇潘,以為這個對象已經(jīng)創(chuàng)建完畢体斩,就直接食用了,其實(shí)User對象并沒有創(chuàng)建完成颖低。
- ****User****類是可變的了絮吵,不可變類所有好處都不復(fù)存在。
終于輪到主角上場的時候了忱屑,利用Builder模式蹬敲,我們可以解決上面的問題,代碼如下:
public class User {
private final String firstName; // 必傳參數(shù)
private final String lastName; // 必傳參數(shù)
private final int age; // 可選參數(shù)
private final String phone; // 可選參數(shù)
private final String address; // 可選參數(shù)
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
public String getPhone() {
return phone;
}
public String getAddress() {
return address;
}
public static class UserBuilder {
private final String firstName;
private final String lastName;
private int age;
private String phone;
private String address;
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
有幾個重要的地方需要強(qiáng)調(diào)一下:
- ****User****類的構(gòu)造方法是私有的莺戒。也就是說調(diào)用者不能直接創(chuàng)建User對象伴嗡。
- ****User****類的屬性都是不可變的。所有的屬性都添加了final修飾符从铲,并且在構(gòu)造方法中設(shè)置了值瘪校。并且,對外只提供getters方法名段。
- Builder模式使用了鏈?zhǔn)秸{(diào)用阱扬。可讀性更佳伸辟。
- Builder的內(nèi)部類構(gòu)造方法中只接收必傳的參數(shù)麻惶,并且該必傳的參數(shù)適用了final修飾符。
相比于前面兩種方法信夫,Builder模式擁有其所有的優(yōu)點(diǎn)用踩,而沒有上述方法中的缺點(diǎn)∶ηǎ客戶端的代碼更容易寫脐彩,并且更重要的是,可讀性非常好姊扔。唯一可能存在的問題就是會產(chǎn)生多余的Builder對象惠奸,消耗內(nèi)存。然而大多數(shù)情況下我們的Builder內(nèi)部類使用的是靜態(tài)修飾的(static)恰梢,所以這個問題也沒多大關(guān)系佛南。
new User.UserBuilder("王", "小二")
.age(20)
.phone("123456789")
.address("亞特蘭蒂斯大陸")
.build();
相當(dāng)整潔,不是嗎嵌言?你甚至可以用一行代碼完成對象的創(chuàng)建嗅回。
經(jīng)典的Builder模式
上面介紹的Builder模式當(dāng)然不是“原生態(tài)”的,經(jīng)典的Builder模式的類圖如下:
Builder:為創(chuàng)建一個Product對象的各個部件制定抽象接口摧茴;
ConcreteBuilder:具體的建造者绵载,它負(fù)責(zé)真正的生產(chǎn);
Director:導(dǎo)演, 建造的執(zhí)行者,它負(fù)責(zé)發(fā)布命令娃豹;
Product:最終消費(fèi)的產(chǎn)品
當(dāng)然焚虱,之前實(shí)例中的Builder模式,是省略掉了Director的懂版,這樣結(jié)構(gòu)更加簡單鹃栽。所以在很多框架源碼中,涉及到Builder模式時躯畴,大多都不是經(jīng)典GOF的Builder模式民鼓,而是省略后的。