原文地址: https://itweknow.cn/detail?id=47 , 歡迎大家訪問。
我們構(gòu)建一個(gè)對(duì)象的幾種方式
- 構(gòu)造器
- 靜態(tài)工廠方法
- 構(gòu)建器
在書中提到了當(dāng)我們遇到了大量的可選參數(shù)時(shí),獲取對(duì)象實(shí)例的幾種方式(重疊構(gòu)造方法,無參構(gòu)造器配合JavaBean的setter方法,構(gòu)建器),書中也分別列出了使用這幾種方式的優(yōu)劣之處。
重疊構(gòu)造器
我們這里也以一個(gè)實(shí)際的例子入手,我們當(dāng)前有一個(gè)用戶類如下:
public class UserConstruct {
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 身份證號(hào)
*/
private String idNumber;
/**
* 地址
*/
private String address;
/**
* 信息是否完整
* true : name,idNumber,address必須都有的情況下
* false : 上述屬性有一個(gè)或多個(gè)為空
*/
private boolean infoIsComplete;
public UserConstruct(Long id, String name) {
this(id, name, null, null);
}
public UserConstruct(Long id, String name, String idNumber) {
this(id, name, idNumber, null);
}
public UserConstruct(Long id, String name, String idNumber, String address) {
this.id = id;
this.name = name;
this.idNumber = idNumber;
this.address = address;
if (null != id && null != name
&& null != idNumber && null != address) {
this.infoIsComplete = true;
}
}
}
例子中的UserConstruct.java
類共有5個(gè)成員變量,其中id
,name
,infoIsComplete
是必選成員變量且infoIsComplete
不是通過用戶設(shè)置的,而是根據(jù)對(duì)象實(shí)際的屬性設(shè)值情況進(jìn)行賦值的。而idNumber
,address
則是兩個(gè)可選變量。
當(dāng)我們需要?jiǎng)?chuàng)建一個(gè)擁有地址而沒有身份證號(hào)碼的對(duì)象時(shí),我們會(huì)這么寫:
UserConstruct user = new UserConstruct(1L, "張三", null, "上海市");
你可能會(huì)想這么看起來并沒有什么問題啊,但是如果當(dāng)我們的可選參數(shù)不是兩個(gè)而是50個(gè)的時(shí)候,構(gòu)造器的參數(shù)會(huì)多么的復(fù)雜難讀,而且稍有不慎就會(huì)出現(xiàn)參數(shù)順序錯(cuò)亂的錯(cuò)誤進(jìn)而導(dǎo)致整個(gè)程序運(yùn)行出錯(cuò)。
也就是說,在可選參數(shù)的數(shù)量多的情況下,雖然重疊構(gòu)造器可行,但是會(huì)有諸如難讀易出錯(cuò)的問題出現(xiàn)。所以不建議使用。
JavaBean的方式
在此模式下,我們通常都是通過調(diào)用一個(gè)無參構(gòu)造器來構(gòu)建對(duì)象,然后通過調(diào)用setter方法來設(shè)置每個(gè)必要參數(shù),以及每個(gè)相關(guān)的可選參數(shù)。我們的用戶類變成了這樣:
public class UserJavaBean {
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 身份證號(hào)
*/
private String idNumber;
/**
* 地址
*/
private String address;
/**
* 信息是否完整
* true : name,idNumber,address必須都有的情況下
* false : 上述屬性有一個(gè)或多個(gè)為空
*/
private boolean infoIsComplete;
public void setId(Long id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
public void setAddress(String address) {
this.address = address;
}
public void setInfoIsComplete(boolean infoIsComplete) {
this.infoIsComplete = infoIsComplete;
}
}
那么在JavaBean模式下我們初始化一個(gè)重疊構(gòu)造器
中例子就變成了下面這樣:
UserJavaBean user = new UserJavaBean();
user.setId(1L);
user.setName("張三");
user.setAddress("上海市");
user.setInfoIsComplete(false);
這樣確實(shí)代碼的可讀性變強(qiáng)了,但是也有那么幾個(gè)缺點(diǎn):
- 無法保證必要參數(shù)
這種方式我們沒辦法保證所有的必要參數(shù)都如我們所愿地被賦上了值,當(dāng)然了這個(gè)缺點(diǎn)還有彌補(bǔ)的方案,就是我們通過調(diào)用一個(gè)包含所有必要參數(shù)的構(gòu)造器來獲取一個(gè)對(duì)象,而只通過setter方法來設(shè)置相關(guān)的可選參數(shù)。 - 構(gòu)造過程中JavaBean可能會(huì)處于不一致的狀態(tài)
對(duì)于這一點(diǎn)書中的描述有點(diǎn)晦澀難懂,不知道是否是翻譯的問題。一個(gè)例子是infoIsComplete
這個(gè)字段必須在所有的屬性都被賦值后才為true
,這樣通過JavaBean的方式我們也沒有很好的方式來通過代碼的方式自動(dòng)為其賦值。
還有一點(diǎn)就是,如果idNumber
和address
兩個(gè)兩個(gè)可選參數(shù)必須同時(shí)存在的時(shí)候,使用JavaBean也是有點(diǎn)力不從心了。 - JavaBean模式阻止了把類變成不可變的可能
一旦我們使用了JavaBean模式,則表明我們會(huì)為類的每個(gè)屬性都編寫一個(gè)共有的setter方法,也就說明我們的類無法成為一個(gè)不可變類。
構(gòu)建器
根據(jù)書中的描述,構(gòu)建器既能保證像重疊構(gòu)造器那樣的安全性,也可以有JavaBean模式那樣的可讀性。
public class UserBuilder {
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 身份證號(hào)
*/
private String idNumber;
/**
* 地址
*/
private String address;
/**
* 信息是否完整
* true : name,idNumber,address必須都有的情況下
* false : 上述屬性有一個(gè)或多個(gè)為空
*/
private boolean infoIsComplete;
/**
* 構(gòu)建器
*/
public static class Builder {
// 必傳參數(shù)
private Long id;
private String name;
// 可選參數(shù)
private String idNumber;
private String address;
public Builder(Long id, String name) {
this.id = id;
this.name = name;
}
public Builder idNumber(String idNumber) {
this.idNumber = idNumber;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public UserBuilder build() {
return new UserBuilder(this);
}
}
private UserBuilder(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.idNumber = builder.idNumber;
this.address = builder.address;
// 根據(jù)參數(shù)賦值情況,給infoIsComplete賦值
if (this.id != null && this.name!= null
&& this.idNumber!= null && this.address != null) {
this.infoIsComplete = true;
}
}
}
還是上面的例子,我們就可以改寫為:
UserBuilder user = new Builder(1L, "張三").address("上海市").build();
是不是同樣的易讀,而且我們還可以在UserBuilder
的構(gòu)造器中進(jìn)行參數(shù)的驗(yàn)證,并且可以順利的給infoIsComplete
屬性自動(dòng)賦值。
構(gòu)建器有下面幾個(gè)優(yōu)點(diǎn):
- 可以和構(gòu)造器一樣對(duì)參數(shù)強(qiáng)加約束條件,比如
idNumber
和address
必須同時(shí)存在。 - 可以擁有多個(gè)可變參數(shù)
- 十分靈活,可以通過單個(gè)Builder構(gòu)建多個(gè)對(duì)象,也可在構(gòu)建對(duì)象時(shí)對(duì)參數(shù)進(jìn)行調(diào)整。
當(dāng)然了,構(gòu)建器也有不足的地方,為了創(chuàng)建一個(gè)對(duì)象,我們首先必須創(chuàng)建一個(gè)它的構(gòu)建器對(duì)象,這可能在一定程度上會(huì)消耗我們的內(nèi)存。所以在一些比較注重性能的情況下構(gòu)建器就不那么好使了。但是在極大多數(shù)的情況下,建議我們在還是在當(dāng)前或者未來參數(shù)數(shù)量比較大的類中使用構(gòu)建器。