第二條:遇到多個構造器參數時考慮用構建器: 1.靜態工廠和構造器有幾個共同的局限性,它們都不能很好的擴展到大量的可選參數.如一個類表示包裝食品外面顯示的營養成分標簽,標簽中有些域是必須的,不過還有多個可選域。大多數產品在某幾個可選域中都有非0值。 2.對于這樣的類,程序員一貫采用重疊構造器(telescoping constructor)模式。在這種模式下,你提供第一個只有必要參數的構造器,第二個構造有一個可選參數,第三個有兩個可選參數,一次類推,最后一個構造器包含所有可選參數。 3.要創建實例的時候,就是參數列表最短的構造器,但其包含了要設置的所有參數--> 不過如果參數是包括一些可選,可選可能有一個為0,如 NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);這個構造器調用通常需要許多你根本不想設置的參數,但還是不能不為他們傳值。如這里給fat傳了一個0. 這里如果僅僅是6個參數的話,情況看起來好像不是特別糟。問題是隨著參數數目的增加,其很快就失去了控制-> 4.一句話:重疊構造器模式可行,但是當有許多參數的時候,客戶端代碼可能會很難編寫,并且依然難以閱讀。如果讀者想知道參數是神馬意思,必須很仔細的要數著這些參數來研究。一連串類型相同的參數會導致一些微妙的錯誤。如果client不小心點到了其中兩個參數的順序,編譯器也不會報錯,但是程序在運行時會出現錯誤的行為。 5.遇到多構造器參數的時候,還有第二種代替辦法,即JavaBeans模式。該模式下,調用一個無參構造函數來創建對象,然后調用setter方法來設置每個必要的參數,以及每個相關的可選參數. 這種模式彌補了重疊構造器模式的不足。說的明白一點,就是創建實例很容易,產生的代碼讀起來也很容易。 不過,JavaBeans模式自身有很嚴重的缺點。因為構造過程被分到了幾個調用中,在構造過程中JavaBean可能處于不一致的狀態。類無法僅僅通過檢驗構造器參數的有效性來保證一致性。試圖使用處于不一致狀態的對象,將會導致失敗。這種失敗與包含錯誤的代碼大相徑庭,因為其調試起來十分困難。 另外不足的一點在于,JavaBeans模式阻止了類做成不可變的可能,這就需要程序員付出額外的努力來保證它的線程安全。 當對象的構造完成,并且不允許在解凍之前使用時,通過手動“凍結”對象,可以彌補這些不足。但是這種方式十分笨拙,在實踐中很少使用。此外,它甚至會在運行時導致錯誤,因為編譯器無法確保程序員會在使用之前先在對象上調用freeze方法。 注:個人認為freeze和鎖類似吧,即保證使用之前先鎖住對象,這樣只有當前線程可進行操作;使用完畢調用解凍方法?釋放鎖--> 6.第三種替代方法,既能保證向重疊構造器模式那樣的安全性,也能保證向JavaBeans模式那么好的可讀性。這就是Builde模式的一種形式。不直接生成想要的對象,而是讓客戶端利用所有必要的參數調用構造器或者靜態工廠,得到一個builder對象。然后客戶端在builder上調用類似setter的方法來設置每個相關的可選參數。最后客戶端調用無參的build方法來生成不可變的對象。這個builder是它構造的類的靜態的成員類。 builder的setter方法返回builder本身,以便可以把調用鏈接起來,以下就是調用代碼: NutritionFact3 cocaCola = new NutritionFact3.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build(); 這樣的客戶端代碼很容易編寫,更為重要的是,易于閱讀。builder模式模擬了具名的可選參數,就是Ada和python一樣。 注:Ada是一種表現能力很強的通用程序設計語言,它是美國國防部為克服軟件開發危機,耗費巨資,歷時近20年研制成功的。它被譽為第四代計算機語言的成功代表。 builder像個構造器一樣,可以對其參數強加約束條件。build方法可以檢驗這些約束條件,將參數從builer拷貝到對象中之后,并在對象域而不是builder域中對他們進行檢驗(注:保護性拷貝:在檢查參數的有效性之前進行,并且有效性檢查是針對拷貝之后的對象)。如果違反了任何約束條件,builder方法就應該拋出IllegalStateException.異常的詳細信息應該顯示出違反了哪個約束條件。 對多個參數強加約束條件的另一種方式是:用多個setter方法對某個約束條件必須持有的所有參數進行檢查。如果該約束條件沒有得到滿足,setter方法就會拋出IllegalArgumentException。這樣有個好處,就是一旦傳遞了無效參數,立即就會發現約束條件失敗,而不是等著調用build方法。 與構造器相比,builder的略微優勢在于,builder可以有多個可變參數。構造器就像方法一樣,只能有一個可變參數。因為builder利用單獨的方法來設置每個參數,你想要多少個可變參數,他們就可以有多少個,直到每個setter方法都有一個可變參數。 builder模式十分靈活,可以利用單個builder構建多個對象。builder的參數可以在創建對象期間進行調整,也可以隨著不同的對象兒改變。builder可以自動填充某些域,例如每次創建對象時自動增加序列號。 設置了參數的builder生成了一個很好的抽象工廠,即客戶端可將將這樣一個builder傳給方法,使得該方法能夠為客戶端創建一個或多個對象。要使用該方法,需要有一個類型表示builder。1.5或者更新版本,用泛型即能滿足所有的builder,無論他們在構建哪種類型的對象。 如: public interface Builder<T> { public T builder(); } 如,可以聲明NutritionFact3.Builder類來實現Builder<NutritionFact3> 帶有Builder實例的方法通常利用有限制的通配符類型來約束構建器的類型參數,如: Tree buildTree(Builder<? extends Node> nodeBuilder),構建每個節點的方法,利用一個客戶端提供的Builder是來來構建樹。 Java中傳統的抽象工廠實現是Class對象,用newInstance方法充當build方法的一部分。這種用法隱含著一些問題。newInstance方法總是企圖調用類的無參構造器,這個構造器可能根本不存在。如果來沒有可以訪問的無參構造器,你也不會收到編譯錯誤。相反,客戶端代碼必須在運行時處理InstaniationException或者IlleagalAccessExcepion.這樣既不雅觀,也不方便。newInstance方法還會傳播由無參構造器拋出的任何異常,及時newInstance缺乏相應的throws子句。換句話說,Class.newInstance破壞了編譯時的異常檢查,而Builder接口彌補了這些不足。 7.Builder模式確實也有其自身不足。為了創建對象,必須先創建它的構建器。雖然創建構建器的開銷在實踐中可能不那么明顯,但是在某些十分注重性能的情況下,可能就成問題了。Builder模式可能還比重疊構造器模式更加冗長,因為它只有在很多參數的時候才使用,比如4個或者跟更多個參數。 不過記住,你將來可能需要添加參數。如果一開始就是用構造器或者靜態工廠,等到類需要多個參數時才添加構建器,就會無法控制。那些過時的構造器或者靜態工廠顯得十分不協調。因此通常最好一開始就是構建器。 簡而言之,如果來的構造器或者靜態工廠中有多個參數。設計這種類時,Builder模式是中不錯的選擇。 特別是當大多數參數都是可選的時候。與是用傳統的重疊構造器模式相比,是用Builder模式的客戶端代碼更易于閱讀和編寫,構建器也比JavaBeans更安全。
部分源碼:
package com.book.chap2.builder;


/** *//**
* 營養成分標簽
* <p>
* 針對多個可選參數的構造器,采用的重疊構造器模式(telescoping constructor)
*
* @author landon
* @since 1.6.0_35
* @version 1.0.0 2013-1-8
*
*/

public class NutritionFacts


{

/** *//** 分量,必須 */
private final int servingSize;// required

/** *//** 一份分量,必須 */
private final int servings;// required

/** *//** 卡路里,可選 */
private final int calories;// optional

/** *//** 脂肪含量 ,可選 */
private final int fat;// optional

/** *//** 鈉含量 ,可選 */
private final int sodium;// optional

/** *//** 碳水化合物 ,可選 */
private final int carbohydrate;// optional


/** *//**
*
* 構造函數1,2個必須成分作為參數
*
* @param servingSize
* @param servings
*/
public NutritionFacts(int servingSize, int servings)

{
this(servingSize, servingSize, 0);
}


/** *//**
*
* 構造函數2,增加一個可選成分:卡路里
*
* @param servingSize
* @param servings
* @param calories
*/
public NutritionFacts(int servingSize, int servings, int calories)

{
this(servingSize, servings, calories, 0);
}


/** *//**
*
* 構造函數3,又增加一個可選成分:脂肪
*
* @param servingSize
* @param servings
* @param calories
* @param fat
*/
public NutritionFacts(int servingSize, int servings, int calories, int fat)

{
this(servingSize, servings, calories, fat, 0);
}


/** *//**
*
* 構造函數4,繼續增加一個可選成分:鈉
*
* @param servingSize
* @param servings
* @param calories
* @param fat
* @param sodium
*/
public NutritionFacts(int servingSize, int servings, int calories,
int fat, int sodium)

{
this(servingSize, servings, calories, fat, sodium, 0);
}


/** *//**
*
* 構造函數5,擁有全部成分
*
* @param servingSize
* @param servings
* @param calories
* @param fat
* @param sodium
* @param carbohydrate
*/
public NutritionFacts(int servingSize, int servings, int calories,
int fat, int sodium, int carbohydrate)

{
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
public static void main(String
args)

{
//fat參數傳了一個0
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
}
}

package com.book.chap2.builder;


/** *//**
*
* 營養成分標簽
* <p>
* 利用JavaBeans模式
*
*@author landon
*@since 1.6.0_35
*@version 1.0.0 2013-1-8
*
*/

public class NutritionFacts2


{
private int servingSize = -1;
private int servings = -1;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
//無參構造函數
public NutritionFacts2()

{
}

//Setters
public void setServingSize(int servingSize)

{
this.servingSize = servingSize;
}

public void setServings(int servings)

{
this.servings = servings;
}

public void setCalories(int calories)

{
this.calories = calories;
}

public void setFat(int fat)

{
this.fat = fat;
}

public void setSodium(int sodium)

{
this.sodium = sodium;
}

public void setCarbohydrate(int carbohydrate)

{
this.carbohydrate = carbohydrate;
}
public static void main(String
args)

{
NutritionFacts2 cocaCola = new NutritionFacts2();
//利用setters
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}

package com.book.chap2.builder;


/** *//**
*
* 營養成分標簽
* <p>
* 使用buider構建器模式來構造多參數的類
*
* @author landon
* @since 1.6.0_35
* @version 1.0.0 2013-1-8
*
*/

public class NutritionFact3


{
//均是final,不可變
private final int servingSize;// required
private final int servings;// required
private final int calories;// optional
private final int fat;// optional
private final int sodium;// optional
private final int carbohydrate;// optional

public static class Builder

{
// 必須參數
private final int servingSize;// required
private final int servings;// required

// 可選參數
private int calories = 0;// optional
private int fat = 0;// optional
private int sodium = 0;// optional
private int carbohydrate = 0;// optional

// 必須參數必須通過通過構造參數傳遞
public Builder(int servingSize, int servings)

{
this.servingSize = servingSize;
this.servings = servings;
}

// 構建calories,返回本身,以便可以把調用連接起來
public Builder calories(int calories)

{
this.calories = calories;
return this;
}

// 構建sodium
public Builder sodium(int sodium)

{
this.sodium = sodium;
return this;
}

// 構建fat
public Builder fat(int fat)

{
this.fat = fat;
return this;
}

// 構建carbohydrate
public Builder carbohydrate(int carbohydrate)

{
this.carbohydrate = carbohydrate;
return this;
}

//build,返回NutritionFact3
public NutritionFact3 build()

{
return new NutritionFact3(this);
}
}

// 隱藏構造函數
private NutritionFact3(Builder builder)

{
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String
args)

{
NutritionFact3 cocaCola = new NutritionFact3.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
}
}

posted on 2013-03-15 15:21
landon 閱讀(2322)
評論(0) 編輯 收藏 所屬分類:
Program 、
Book