在日常開發(fā)中總是會遇到多參數(shù)的情況,那么對于多參數(shù),尤其是可選參數(shù)眾多的情況,可能有如下的一些解決方案.
重疊構(gòu)造器模式
重疊構(gòu)造器模式在Java代碼中很常見,其解決的問題是參數(shù)過多情況下又不想給調(diào)用方帶來過多的實例化對象負(fù)擔(dān).在這種情況下調(diào)用方只需要選擇一個適合自己的構(gòu)造函數(shù)調(diào)用就好.
public Configuration(Integer maxConnect) {
this(maxConnect, 0);
}
public Configuration(Integer maxConnect, Integer minConnect) {
this("default", maxConnect, minConnect);
}
public Configuration(String password, Integer maxConnect, Integer minConnect) {
this(...)
}
然而事實總不是那么如人所愿,這個模式有很多缺點(diǎn).
- 層層嵌套,導(dǎo)致整個實例化過程其實是一條直線,一通到底,也就注定了其過程不夠靈活.
- 對于參數(shù)較少的構(gòu)造函數(shù)不得不弄一堆的默認(rèn)值填充,導(dǎo)致其看起來不是很優(yōu)雅.
- 增加參數(shù)對于這種模式無疑是困難重重,需要從底到上一層一層修改.
工廠模式
工廠模式本意在于封裝具體的創(chuàng)建流程,提供出簡單便捷的入口,但是在多參數(shù)情況下其能改進(jìn)的只是讓實例化過程不再是一條直線,工廠中可以根據(jù)具體參數(shù)制造出Configuration及其子類.其本質(zhì)與重疊構(gòu)造器模式并沒有太大的區(qū)別,只是把構(gòu)造器邏輯提取到相應(yīng)工廠,所以工廠模式并不能解決上述問題.
public static Configuration newInstance(String password) {
return new Configuration("default", password, "default");
}
public static Configuration newInstance(String password, String username) {
return new Configuration(username, password, "default");
}
public static Configuration newInstance(String password, String username, String url,) {
return new Configuration(username, password, url);
}
JavaBean模式
嚴(yán)格的JavaBean是只有空構(gòu)造函數(shù),其他屬性一律使用set方法,當(dāng)然必要參數(shù)可以放在構(gòu)造函數(shù)中,那么就變成下面的這種形式.
Configuration configuration = new Configuration();
configuration.setPassword("default");
configuration.setUrl("http://mrdear.cn");
configuration.setUsername("default");
雖然少了冗長的參數(shù)列表,但是缺點(diǎn)也是很明顯:
- 對象的創(chuàng)建過程被分解,按照意圖,new的過程就是創(chuàng)建,剩下的一律不算創(chuàng)建,但這種模式下的創(chuàng)建實際上是兩步,創(chuàng)建與填值.
- 對修改開放,該模式暴露了過多set方法,使得任意能獲取到該實例的地方都可以隨意修改器內(nèi)容,對于全局性的config實例或者其他單例實例這是致命的缺點(diǎn).
Builder模式
有句話說得好,遇到難以解決的問題就加一層中間層來代理抽象.Builder模式正式如此,對象本身創(chuàng)建麻煩,那么就使用一個代理對象來主導(dǎo)創(chuàng)建與檢驗,兼顧了重疊器模式的安全性以及JavaBean模式的靈活性.
public class Configuration {
private String username;
private String password;
private String url;
public Configuration(String username, String password, String url) {
this.username = username;
this.password = password;
this.url = url;
}
public static ConfigurationBuilder builder() {
return new ConfigurationBuilder();
}
public static class ConfigurationBuilder {
private String username;
private String password;
private String url;
ConfigurationBuilder() {
}
public ConfigurationBuilder username(String username) {
this.username = username;
return this;
}
public ConfigurationBuilder password(String password) {
this.password = password;
return this;
}
public ConfigurationBuilder url(String url) {
this.url = url;
return this;
}
public Configuration build() {
// can some check
return new Configuration(this.username, this.password, this.url);
}
public String toString() {
return new StringBuilder().append((String)"Configuration.ConfigurationBuilder(username=").append((String)this.username).append((String)", password=").append((String)this.password).append((String)", url=").append((String)this.url).append((String)")").toString();
}
}
}
如上面代碼,客戶端使用Builder對象選擇必要的參數(shù),然后最后build()構(gòu)建出自己想要的參數(shù).Builder有很多優(yōu)勢,也很靈活:
- 把線性的構(gòu)造結(jié)構(gòu)用
build方法變成了分支結(jié)構(gòu),你可以使用build構(gòu)造該類的子類以及其他相關(guān)類. - 很靈活,組合的形式可以在各自builder加強(qiáng)約束校驗,并且這些業(yè)務(wù)邏輯不會在污染你的原類.當(dāng)不符合的參數(shù)應(yīng)及時拋出
IllegalArgumentException - 可作為參數(shù)傳遞,比如Mybatis中就大量使用了這種傳遞方式讓客戶端更加方便的構(gòu)造配置類.
- 使用
.filed()形式構(gòu)建參數(shù),只要命名有一定規(guī)范,就很清楚參數(shù)的作用,編寫出來的代碼也更加容易閱讀,不用點(diǎn)進(jìn)去看具體參數(shù)來選擇適合自己的方法了.
當(dāng)然缺點(diǎn)也有:
- 構(gòu)造想要的類之前必須構(gòu)造一個builder中間類,對于一些經(jīng)常循環(huán)中實例化的類是很不適合的.大量對象被重復(fù)創(chuàng)建會帶來性能上的影響.因此對于一些復(fù)雜的配置類使用builder時最合適不過的了.
Mybatis中Builder模式應(yīng)用
Mybatis擁有種類繁多的配置,那么builder就很適合其配置類對象,以MappedStatement類為例子.
MappedStatement擁有數(shù)十項配置,如果使用構(gòu)造函數(shù)或者靜態(tài)工廠那么對于開發(fā)人員可能是難以接受的體驗.一大堆參數(shù),還需要點(diǎn)進(jìn)去才能知道每一個參數(shù)的意義,在這樣的情況下Builder模式就是一個很好的解決方式.
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
......
}
org.apache.ibatis.mapping.MappedStatement.Builder作為MappedStatement的靜態(tài)內(nèi)部類,擁有可以訪問MappedStatement任意屬性的權(quán)利.那么其就可以直接實例化mappedStatement對象,然后使用該對象直接訪問屬性,從而簡化Builder模式,也很好的創(chuàng)建出MappedStatement的實例.
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
mappedStatement.resultMaps = new ArrayList<ResultMap>();
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
String logId = id;
if (configuration.getLogPrefix() != null) {
logId = configuration.getLogPrefix() + id;
}
mappedStatement.statementLog = LogFactory.getLog(logId);
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
public Builder resource(String resource) {
mappedStatement.resource = resource;
return this;
}
......
}
另外為了保證MappedStatement對象必須使用Builder來控制,代碼中把其構(gòu)造函數(shù)聲明為包級別權(quán)限
MappedStatement() {
// constructor disabled
}
總結(jié)
Builder模式本質(zhì)上是一種特殊的工廠模式,按照流水線方式調(diào)用,然后最后檢查產(chǎn)品是否合格,流水線之間可以任意組合,達(dá)到了高度的靈活性.
參考
Effective Java : 遇到多個構(gòu)造器參數(shù)時考慮構(gòu)建器(Builder模式)