來自《effective+java中文版》:
靜態工廠和構造器有一個共同的侷限性,都不能很好的擴展到大量的可選參數。
重疊構造器模式
如果我們有一個類,表示一個用戶的個人信息,有些值是必須的,有些不是必須的。
必須的參數:用戶名、年齡、性別
非必須的參數:身高、住址、體重
public class User {
private String username;
private int age;
private int sex;
private float height;
private String address;
private float weight;
public User(String username,int age,int sex){
this(username,age,sex,0);
}
public User(String username,int age,int sex,float height){
this(username,age,sex,height,"");
}
public User(String username,int age,int sex,float height,String address){
this(username,age,sex,height,address,0f);
}
public User(String username,int age,int sex,float height,String address,float weight){
this.username = username;
this.age = age;
this.sex = sex;
this.height = height;
this.address = address;
this.weight = weight;
}
}
當你想要創建實例的時候,利用參數列表最短的構造器,該列表包含了要設置的參數:
User u = new User("china", 23, 1, 55.5f, "",179.2f);
這個構造器調用通常許多你不想設置的參數,但還是不得不爲他們傳值。隨着屬性列表的增多,很快就會失去控制。
重疊構造器模式可行,但是當有許多參數的時候,客戶端代碼會很難寫,並且難以閱讀。
如果讀者想知道某一個參數是什麼意思,還需要仔細的數着參數個數來嘆究竟。可能會導致微妙的錯誤,很可能一不小心兩個參數位置顛倒了,微妙的錯誤也可能影響極大的後患。
遇到許多構造器參數的時候,還有一種javabbeans模式,這種模式只需要一個午餐的構造方法,然後通過setter方法給每個必要的參數和不必要的參數進行賦值:
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
這種模式彌補了重疊構造器模式的不足,創建實例容易,代碼也易讀。
但是,javabeans模式本身有着很嚴重的缺點,因爲構造過程被分到幾個調用中,構造過程中可能出現javabean不一致的狀態,類無法僅僅通過構造器參數的有效性來保證一致性,試圖使用處於不一致的狀態,將會導致失敗。javabean模式阻止了把類做成不可變的可能。程序員需要通過額外的努力來保證線程的安全。
幸運的是,還有第三種方法,既能保證像重疊器模式那樣的安全性,也能保證javabeans模式那樣的可讀性,這就是Builder模式的一種形式。不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用構造器,得到一個builder對象,然後客戶端在builder對象上調用類似setter的方法,來設置每個相關的可選參數。最後客戶端調用無參的builder方法生成不可變的對象。實例:
public class User {
private String username;
private int age;
private int sex;
private float height;
private String address;
private float weight;
public User(Builder builder) {
username = builder.username;
age = builder.age;
sex = builder.sex;
height = builder.height;
address = builder.address;
weight = builder.weight;
}
public static class Builder {
// required
private String username = "";
private int age = 0;
private int sex = 1;
// optional
private float height = 0.0f;
private String address = "";
private float weight = 0.0f;
public Builder(String username, int age, int sex) {
this.username = username;
this.age = age;
this.sex = sex;
}
public Builder height(float val) {
height = val;
return this;
}
public Builder address(String val) {
address = val;
return this;
}
public Builder weight(float val) {
weight = val;
return this;
}
public User build() {
return new User(this);
}
}
public String getUsername() {
return username;
}
public int getAge() {
return age;
}
public int getSex() {
return sex;
}
public float getHeight() {
return height;
}
public String getAddress() {
return address;
}
public float getWeight() {
return weight;
}
}
調用代碼:
public class TestBuilder {
public static void main(String[] args) {
User user = new User.Builder("china",23,1).address("hangzhou").build();
System.out.println(user.getUsername()+","+user.getAge()+","+user.getSex()+","+user.getAddress());
}
}
打印出:
china,23,1,hangzhou。
同樣,還可以設置更多參數:
User user = new User.Builder("china",23,1).address("hangzhou").height(1.78f).build();
user.getHeight()可以獲取到:1.78
這樣客戶端代碼更容易編寫,更易於閱讀。