有時,我們在寫一個構造函數時,經常因爲它包含衆多的參數而苦惱,這時可以考慮用Builder模式來創建對象。
如,我們要設計一個營養成份的類,包含能量,蛋白質,脂肪,鈣,鐵,鋅,維生素A, 維生素B1 ... 等,但在構造的時候,不一定每次都需要這些參數,如鈣,鐵,鋅和維生素等是可選的,爲了適應多種可能的搭配,比較原始的辦法就是採用telescoping constructor模式,例子如下。
public class Nutrition
{
private int calories;
private int protein;
private int fat;
private int ca;
private int fe;
private int Va;
private int Vb;
...
public Nutrition(int cal, int pro){...}
public Nutrition(int cal, int pro, int fat){...}
public Nutrition(int cal, int pro, int fat, int ca){...}
public Nutrition(int cal, int pro, int fat, int ca, int fe){...}
public Nutrition(int cal, int pro, int fat, int ca, int fe, int ){...}
public Nutrition(int cal, int pro, int fat, int ca, int fe, int Va){...}
.......
}
這種方法的缺點很明顯,一個函數的參數一旦超過3個,用戶就很容易把順序搞混,而更杯具的是這種情況編譯器無法識別,非常不易查錯。
第二種方法是JavaBean模式。
public class Nutrition
{
private int calories;
private int protein;
private int fat;
private int ca;
private int fe;
private int Va;
private int Vb;
...
public Nutrition(){...}
public setCal(int cal);
public setPro(int pro);
public setfat(int fat);
public setca(int ca);
public setfe(int fe);
.......
}
這種方式的缺點是在整個構造對象的過程中,其狀態不是一致的(inconsistent state),即在創建好一個對象後,這個對象的狀態在後面的某個時候內仍然是在變化的(因爲其值發生了改變),此外,這種方法只能構造一個可變(mutable)對象,必須採用附加的方法保證線程安全。
所以,書中推薦了第三種方法利用Builder來構造。
public class Nutrition
{
private final int calories;
private final int protein;
private final int fat;
private final int ca;
private final int fe;
private final int Va;
private final int Vb;
public static class Builder {
private final int calories; //必有參數
private final int protein; //必有參數
private final int fat; //可選參數
private final int ca; //可選參數
private final int fe; //可選參數
private final int Va; //可選參數
private final int Vb; //可選參數
public Builder(int cal,int pro) {...};
public Builder fat(int fat){...};
public Builder ca(int ca){...};
public Builder fe(int fe){...};
public Nutrition builder(Builder builder)
{
return new Nutrition(this);
}
} //end of Builder
private Nutrition(Builder builder)
{
calories = builder.calories;
protein = builder.protein;
ca = builder.ca;
fe = builder.fe;
....
}
}
使用時可以採用以下代碼:
Nutrition nu = new Nutrition.Builder(1,2).fat(45).ca(456).fe(4).builder();
其中,Builder()中的是必選參數,其他的是可選參數。
採用這種方式,代碼易讀易寫,參數初始化的順序也無關緊要。
當然,它也有缺點。它必須先構造一個Builder對象,其開銷在性能問題很關鍵的場合的是不適用的。另外,相比之下,Builder模式比前面現兩種模式更加複雜,如果不是有太多的參數的話,就沒有必要使用這種模式。