摘要:因此,最好一開始就考慮使用構(gòu)造器。與使用傳統(tǒng)的重疊構(gòu)造器模式相比,使用模式的客戶端代碼更易于閱讀和編寫,構(gòu)建器也比更加安全。
??靜態(tài)工廠和構(gòu)造器有個共同的局限性:他們都不能很好地擴展到大量的可選參數(shù)??紤]用一個類表示包裝食品外面顯示的營養(yǎng)成分標(biāo)簽。這些標(biāo)簽中有幾個域是必需的:每份的含量、每罐的含量以及每份的卡路里,還有超過20個可選域:總脂肪、飽和脂肪量、轉(zhuǎn)化脂肪、膽固醇、鈉等等。大多數(shù)產(chǎn)品在某幾個可選域中都會有非零的值。
??對于這樣的類,應(yīng)該采用哪種構(gòu)造器或者靜態(tài)方法來編寫呢?程序猿一向習(xí)慣采用重疊構(gòu)造器(telescoping constructor)模式,在這種模式下,提供一個只有必要參數(shù)的構(gòu)造器,第二個構(gòu)造器有一個可選參數(shù),第三個有兩個可選參數(shù),以此類推,最后一個構(gòu)造器包含所有可選參數(shù)。下面有個示例,為了簡單起見,它顯示四個可選域:
// Telescoping constructor pattern - does not scale well! public class NutritionFacts { private final int servingSize; // (mL) required private final int servings; // (per container) required private final int calories; // (per serving) optional private final int fat; // (g/serving) optional private final int sodium; // (mg/serving) optional private final int carbohydrate; // (g/serving) optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } 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; } }
??當(dāng)你想要創(chuàng)建實例的時候,就利用參數(shù)列表最短的構(gòu)造器,但該列表中包含了要設(shè)置的所有參數(shù):NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);這個構(gòu)造器調(diào)用通常需要許多你本不想設(shè)置的參數(shù),但還是不得不為它們傳遞值。在這個例子中,我們給fat傳遞了一個值為0。如果“僅僅”是這6個參數(shù),看起來還不算太糟,問題是隨著參數(shù)數(shù)目的增加,它很快就失去了控制。
??總的來說,使用重疊構(gòu)造器模式是可行的,但是當(dāng)有很多參數(shù)的時候就很難編寫客戶端代碼,也很難去閱讀它們。如果讀者想要知道這些值代表什么意思,就必須仔細(xì)地數(shù)著這些參數(shù)來探個究竟。一長串類型相同的參數(shù)會導(dǎo)致一些微妙的錯誤,如果客戶端不小心顛倒了期中兩個參數(shù)的順序,編譯器也不會報錯,但是程序在運行的時候就會出現(xiàn)錯誤的行為。
??遇到許多構(gòu)造器參數(shù)的時候,還有第二種代替方法,即JavaBean模式,在這種模式下,調(diào)用一個無參構(gòu)造器來創(chuàng)建對象,然后調(diào)用setter方法來設(shè)置每個必要的參數(shù),以及每個相關(guān)的可選參數(shù):
// JavaBeans Pattern - allows inconsistency, mandates mutability public class NutritionFacts { // Parameters initialized to default values (if any) private int servingSize = -1; // Required; no default value private int servings = -1; // Required; no default value private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public NutritionFacts() { } // Setters public void setServingSize(int val) { servingSize = val; } public void setServings(int val) { servings = val; } public void setCalories(int val) { calories = val; } public void setFat(int val) { fat = val; } public void setSodium(int val) { sodium = val; } public void setCarbohydrate(int val) { carbohydrate = val; } }
??這種模式彌補了重疊構(gòu)造器模式的不足。說得明白一點,就是創(chuàng)建實例很容易,這樣產(chǎn)生的代碼讀起來也很容易。
NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setServings(8); cocaCola.setCalories(100); cocaCola.setSodium(35); cocaCola.setCarbohydrate(27);
??遺憾的是,這種JavaBean模式自身有很嚴(yán)重的缺點。因為構(gòu)造過程被分到了幾個調(diào)用中,在構(gòu)造的過程中JavaBean可能處于不一致的狀態(tài)。類無法通過檢驗構(gòu)造器參數(shù)的有效性來保證一致性。試圖使用處于不一致狀態(tài)的對象,將會導(dǎo)致失敗,這種失敗與包含錯誤的代碼大相徑庭,因此它調(diào)試起來十分困難。與此相關(guān)的另一點不足在于,JavaBean模式阻止了把類做成了不可變的可能(第17項),這就需要程序猿付出額外的努力來保證它的線程安全。
??在構(gòu)造器完成構(gòu)造對象之前進行加鎖,完成構(gòu)造之后進行解鎖,這就能彌補以上的不足之處,但是這種方式十分笨拙,在實踐中很少使用。此外,它甚至?xí)谶\行時出現(xiàn)錯誤,因為編譯器無法確保程序猿會在使用構(gòu)造器之前進行加鎖操作。
??幸運的是,還有第三種替代方法,結(jié)合了重疊構(gòu)造器的安全性和JavaBean模式的可讀性。這就是Builder模式 [Gamma95] 的一種形式。不直接生成想要的對象,而是讓客戶端調(diào)用一個帶有所有必需參數(shù)的構(gòu)造器方法(或者靜態(tài)工廠方法)去獲得一個builder對象,然后客戶端在builder對象上調(diào)用類似于setter的方法來設(shè)置每個相關(guān)的可選參數(shù)。最后,客戶端調(diào)用無參的build方法來生成不可變的對象。這個builder通常是它構(gòu)建的類的靜態(tài)成員類(第24項)。下面就是它的示例:
// Builder Pattern public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { // Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } }
??NutritionFacts是不可變的,所有默認(rèn)參數(shù)值都多帶帶放在一個地方。builder的setter方法返回的是builder本身,以便可以把調(diào)用連接起來。下面是客戶端代碼:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
??這樣的客戶端代碼是很容易編寫的,更重要的是,閱讀起來很容易。Builder模式模仿了Python和Scala中的命名可選參數(shù)。
??為簡潔起見,省略了有效性檢查。 要盡快檢測無效參數(shù),請在構(gòu)建器的構(gòu)造函數(shù)和方法中檢查參數(shù)有效性。 檢查構(gòu)建方法調(diào)用的構(gòu)造函數(shù)中涉及多個參數(shù)的不變量。 要確保這些不變量不受攻擊,請在從構(gòu)建器復(fù)制參數(shù)后對對象字段執(zhí)行檢查(第50項)。 如果檢查失敗,則拋出IllegalArgumentException(第72項),其詳細(xì)消息指示哪些參數(shù)無效(第75項)。
??Builder模式非常適合類層次結(jié)構(gòu)。 使用并行的構(gòu)建器層次結(jié)構(gòu),每個構(gòu)建器都嵌套在相應(yīng)的類中。 抽象類有抽象構(gòu)建器; 具體課程有混凝土建造者。例如,在代表各種批薩的層次結(jié)構(gòu)的根部考慮使用一個抽象類:
// Builder pattern for class hierarchies public abstract class Pizza { public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE } final Settoppings; abstract static class Builder > { EnumSet toppings = EnumSet.noneOf(Topping.class); public T addTopping(Topping topping) { toppings.add(Objects.requireNonNull(topping)); return self(); } abstract Pizza build(); // Subclasses must override this method to return "this" protected abstract T self(); } Pizza(Builder> builder) { toppings = builder.toppings.clone(); // See Item 50 } }
??注意下這個Pizza類,Builder是具有遞歸類型參數(shù)的通用類型(第30項)。 這與抽象方法self一起允許方法鏈在子類中正常工作,而不需要強制轉(zhuǎn)換。Java缺乏自我類型這一事實的解決方法被稱為模擬自我類型習(xí)語(This workaround for the fact that Java lacks a self type is known as the simulated self-type idiom.)。
??這是Pizza的兩個具體子類,其中一個代表標(biāo)準(zhǔn)的紐約式披薩,另一個代表calzone。 前者具有所需的大小參數(shù),而后者允許你指定醬汁應(yīng)該在內(nèi)部還是外部:
public class NyPizza extends Pizza { public enum Size { SMALL, MEDIUM, LARGE } private final Size size; public static class Builder extends Pizza.Builder{ private final Size size; public Builder(Size size) { this.size = Objects.requireNonNull(size); } @Override public NyPizza build() { return new NyPizza(this); } @Override protected Builder self() { return this; } } private NyPizza(Builder builder) { super(builder); size = builder.size; } } public class Calzone extends Pizza { private final boolean sauceInside; public static class Builder extends Pizza.Builder { private boolean sauceInside = false; // Default public Builder sauceInside() { sauceInside = true; return this; } @Override public Calzone build() { return new Calzone(this); } @Override protected Builder self() { return this; } } private Calzone(Builder builder) { super(builder); sauceInside = builder.sauceInside; } }
??請注意,每個子類的構(gòu)建器中的構(gòu)建方法被聲明為返回正確的子類:NyPizza.Builder的構(gòu)建方法返回NyPizza,而Calzone.Builder中的構(gòu)建方法返回Calzone。這種技術(shù),其中子類方法聲明的返回類型是在超類中聲明的返回類型的子類型,稱為協(xié)變返回類型,它允許客戶使用這些構(gòu)建器而無需進行創(chuàng)建。
??這些“分層構(gòu)建器”的客戶端代碼基本上與簡單的NutritionFacts構(gòu)建器的代碼相同。為簡潔起見,下面顯示的示例客戶端代碼假定枚舉常量上的靜態(tài)導(dǎo)入:
NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build(); Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build();
??構(gòu)建器相對于構(gòu)造函數(shù)的一個小優(yōu)點是構(gòu)建器可以有多個可變參數(shù),因為每個參數(shù)都是在自己的方法中指定的。除此之外,構(gòu)建器可以將傳遞給一個方法的多個參數(shù)通過多次調(diào)用方法的方式聚合到一個字段中,就如之前addTopping方法中所演示的那樣。
??Builder模式非常靈活。 可以重復(fù)使用單個構(gòu)建器來構(gòu)建多個對象。 可以在構(gòu)建方法的調(diào)用之間調(diào)整構(gòu)建器的參數(shù),以改變創(chuàng)建的對象。構(gòu)建器可以在創(chuàng)建對象時自動填充某些字段,例如每次創(chuàng)建對象時增加的序列號。
??Builder模式也有缺點。 要創(chuàng)建對象,必須先創(chuàng)建其構(gòu)建器。 雖然在實踐中創(chuàng)建此構(gòu)建器的成本不太可能明顯,但在性能關(guān)鍵的情況下可能會出現(xiàn)問題。此外,Builder模式比重疊構(gòu)造函數(shù)的模式更冗長,因此只有在有足夠的參數(shù)(例如四個或更多)時才值得去使用它。 但請記住,你可能希望在將來添加更多的參數(shù)。但是如果你從構(gòu)造函數(shù)或靜態(tài)工廠開始并在類進化到參數(shù)數(shù)量失控時才切換到構(gòu)建器,那些過時的構(gòu)造器和靜態(tài)工廠就會顯得非常不協(xié)調(diào)。因此,最好一開始就考慮使用構(gòu)造器。
??簡而言之,如果類的構(gòu)造器或者靜態(tài)工廠方法中具有多個參數(shù),設(shè)計這種類時,Builder模式就是種不錯的選擇,特別是當(dāng)大多數(shù)參數(shù)都是可選的時候。與使用傳統(tǒng)的重疊構(gòu)造器模式相比,使用Builder模式的客戶端代碼更易于閱讀和編寫,構(gòu)建器也比JavaBean更加安全。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/73875.html
摘要:本章中的大部分內(nèi)容適用于構(gòu)造函數(shù)和方法。第項其他方法優(yōu)先于序列化第項謹(jǐn)慎地實現(xiàn)接口第項考慮使用自定義的序列化形式第項保護性地編寫方法第項對于實例控制,枚舉類型優(yōu)先于第項考慮用序列化代理代替序列化實例附錄與第版中項目的對應(yīng)關(guān)系參考文獻 effective-java-third-edition 介紹 Effective Java 第三版全文翻譯,純屬個人業(yè)余翻譯,不合理的地方,望指正,感激...
摘要:并沒有類繼承模型,而是使用原型對象進行原型式繼承。我們舉例說明原型鏈查找機制當(dāng)訪問一個對象的屬性時,會從對象本身開始往上遍歷整個原型鏈,直到找到對應(yīng)屬性為止。原始類型有以下五種型。此外,試圖查找一個不存在屬性時將會遍歷整個原型鏈。 Javascript 并沒有類繼承模型,而是使用原型對象 prototype 進行原型式繼承。 盡管人們經(jīng)常將此看做是 Javascript 的一個缺點,然...
摘要:推薦序前言致謝第一章引言第二章創(chuàng)建和銷毀對象第項用靜態(tài)工廠方法代替構(gòu)造器第項遇到多個構(gòu)造器參數(shù)時要考慮使用構(gòu)建器第項用私有構(gòu)造器或者枚舉類型強化屬性第項通過私有構(gòu)造器強化不可實例化的能力第項優(yōu)先考慮依賴注入來引用資源第項避免創(chuàng)建不必要的對象 推薦序 前言 致謝 第一章 引言 第二章 創(chuàng)建和銷毀對象 第1項:用靜態(tài)工廠方法代替構(gòu)造器 第2項:遇到多個構(gòu)造器參數(shù)時要考慮使用構(gòu)建器 第...
摘要:一個類可以提供一個公共靜態(tài)工廠方法,它僅僅是一第項遇到多個構(gòu)造器參數(shù)時要考慮使用構(gòu)建器靜態(tài)工廠和構(gòu)造器有個共同的局限性他們都不能很好地擴展到大量的可選參數(shù)。 ??本章涉及創(chuàng)建和銷毀對象,包括何時以及如何創(chuàng)建它們,何時以及如何避免創(chuàng)建它們,如何確保它們被及時銷毀,以及如何管理在銷毀之前必須進行的清理操作。 第1項:用靜態(tài)工廠方法代替構(gòu)造器 ??類允許客戶端獲取實例的傳統(tǒng)方法是提供公共構(gòu)造...
閱讀 1335·2021-09-27 13:35
閱讀 2652·2021-09-06 15:12
閱讀 3452·2019-08-30 15:55
閱讀 2900·2019-08-30 15:43
閱讀 488·2019-08-29 16:42
閱讀 3505·2019-08-29 15:39
閱讀 3128·2019-08-29 12:28
閱讀 1303·2019-08-29 11:11