靜態(tài)工廠和構(gòu)造函數(shù)共同的局限性:不能很好地適應(yīng)大量的可選參數(shù)徐裸。
重疊構(gòu)造函數(shù)模式
第一個只有必要參數(shù)的構(gòu)造函數(shù)遣鼓,
第二個構(gòu)造函數(shù)有一個可選參數(shù),
第三個構(gòu)造函數(shù)有兩個可選參數(shù)重贺。
最后一個構(gòu)造函數(shù)包含所有可選參數(shù)
重疊構(gòu)造函數(shù)的缺點
這個構(gòu)造函數(shù)有許多不想設(shè)置的參數(shù)骑祟,但是還是不得不為它們傳值。
隨著參數(shù)數(shù)目的增加气笙,很快就會失控次企。
重疊構(gòu)造函數(shù)模式可行,但是當(dāng)有許多參數(shù)的時候潜圃,客戶端代碼就很難編寫缸棵,并且仍然較難以閱讀。
如果讀者想要知道哪些值是什么意思谭期,必須很仔細地數(shù)著這些參數(shù)來探個究竟堵第。
一長串類型相同的參數(shù)會導(dǎo)致一些微妙的錯誤吧凉。如果客戶端不小心顛倒了其中兩個參數(shù)的順序,編譯器也不會報錯踏志,但是在程序運行的時候會出現(xiàn)錯誤的行為阀捅。
JavaBeans模式
調(diào)用一個無參數(shù)的構(gòu)造函數(shù)來創(chuàng)建對象,然后調(diào)用set方法來設(shè)置每個必要的參數(shù)狰贯,以及每個相關(guān)的可選參數(shù)也搓。
這種模式彌補了重疊構(gòu)造函數(shù)模式的不足:代碼容易閱讀。
JavaBeans模式的不足
構(gòu)造過程被分到了幾個調(diào)用中涵紊,在構(gòu)造過程中JavaBean可能處于不一致的狀態(tài)。類無法僅僅通過檢驗構(gòu)造函數(shù)參數(shù)的有效性來保證一致性幔摸。試圖使用處于不一致狀態(tài)的對象摸柄,將會導(dǎo)致失敗,這種失敗與包含錯誤的代碼是不同的既忆,調(diào)試起來非常困難驱负。
JavaBeans模式阻止了把類做成不可變類的可能,這需要程序員付出額外的努力來保證它的線程安全患雇。
JavaBeans模式不足的解決辦法
思路:對象構(gòu)造完成跃脊,不允許在解凍之前使用
通過手工“凍結(jié)”對象,可以彌補這些不足苛吱,但是這種方式十分笨拙酪术,在實踐中很少使用。此外翠储,它甚至?xí)谶\行時導(dǎo)致錯誤绘雁,因為編譯器無法確保程序員會在使用之前先在對象上調(diào)用freeze
方法。
Builder模式
保證像重疊構(gòu)造函數(shù)模式那樣的安全性援所,也能保證像JavaBeans模式那么好的可讀性庐舟。
不直接生成想要的對象,而是讓客戶端利用所有必要的參數(shù)調(diào)用構(gòu)造函數(shù)(或靜態(tài)工廠)住拭,得到一個builder對象挪略。
然后客戶端在builder對象上調(diào)用類似于set的方法,來設(shè)置每個相關(guān)的可選參數(shù)滔岳。
最后客戶端調(diào)用無參的build方法來生成不可變的對象杠娱。
builder是它構(gòu)建的類的靜態(tài)成員類。
注意NutritionFacts是不可變的澈蟆,所有的默認參數(shù)值都單獨放在一個地方墨辛。builder的setter方法返回builder本身,以便可以鏈?zhǔn)秸{(diào)用趴俘。
這樣的客戶端代碼很容易編寫和閱讀睹簇。builder模式模擬了具名的可選參數(shù)奏赘。
builder像個構(gòu)造函數(shù)一樣,可以對其參數(shù)強加約束條件太惠。build方法可以檢驗這些約束條件磨淌,將參數(shù)從builder拷貝到對象中之后,并在對象域而不是builder域中對它們進行校驗凿渊,這一點很重要梁只。
如果違反了任何約束條件,build方法就應(yīng)該拋出IllegalStateException異常埃脏。
異常的詳細信息應(yīng)該顯示出違反了那個約束條件搪锣。
Builder模式對多個參數(shù)強加約束條件的方法
用多個set方法對某個約束條件必須持有的所有參數(shù)進行檢查。如果該約束條件沒有得到滿足彩掐,set方法就會拋出IllegalArgumentException构舟。
這樣有個好處,就是一旦傳遞了無效的參數(shù)堵幽,立即就會發(fā)現(xiàn)約束條件無效狗超,而不是等著調(diào)用build方法。
builder模式與構(gòu)造函數(shù)的對比
builder模式可以有多個可變的參數(shù)(多個set方法)朴下。構(gòu)造函數(shù)只能有一個可變參數(shù)努咐。
builder模式靈活性
可以利用單個builder構(gòu)建多個對象。
builder的參數(shù)可以在創(chuàng)建對象期間進行調(diào)整殴胧,也可以隨著不同的對象而改變渗稍。builder可以自動填充某些域,例如每次創(chuàng)建對象時自動增加序列號溃肪。
builder是一個抽象工廠
設(shè)置了參數(shù)的builder是一個很好的抽象工廠免胃。
客戶端可以將這樣的一個builder傳給方法,使該方法能夠為客戶端創(chuàng)建一個或多個對象惫撰。要這樣使用羔沙,必須有一個類型來表示builder。
NutritionFacts.Builder類可以實現(xiàn)Builder<NutritionFacts>接口厨钻。
帶有Builder實例的方法利用有限制的通配符類型來約束構(gòu)建器的類型參數(shù)扼雏。
Java傳統(tǒng)的抽象工廠
Java傳統(tǒng)的抽象工廠是Class對象,用newInstance方法充當(dāng)build方法的一部分夯膀。
這種用法隱含著許多問題:
newInstance方法總是企圖調(diào)用類的無參構(gòu)造函數(shù)诗充,這個構(gòu)造函數(shù)甚至可能根本不存在。如果類沒有可以訪問的無參構(gòu)造函數(shù)诱建,也不會收到編譯時錯誤蝴蜓。相反,客戶端代碼必須在運行時處理InstantiationException或IllegalAccessException,這樣既不雅觀也不方便茎匠。
newInstance方法還會傳播由無參構(gòu)造函數(shù)拋出的任何異常格仲。
Class.newInstance破壞了編譯時的異常檢查。Builder接口彌補了這些不足诵冒。
Builder模式的不足
為了創(chuàng)建對象凯肋,必須先創(chuàng)建構(gòu)建器。
builder模式比重疊構(gòu)造函數(shù)模式更加冗長汽馋,因此它只在有很多參數(shù)的時候才使用(四個以上)侮东。
但是如果預(yù)測到類將會添加更多的參數(shù),可以一開始就使用構(gòu)建器豹芯,這樣就不會有過時的構(gòu)造函數(shù)或靜態(tài)工廠悄雅。
總結(jié)
如果類的構(gòu)造函數(shù)或靜態(tài)工廠中具有多個參數(shù),設(shè)計這種類時告组,Builder模式是不錯的選擇煤伟。
與傳統(tǒng)的重疊構(gòu)造函數(shù)模式相比,使用Builder模式代碼更易于閱讀和編寫木缝,構(gòu)建器比JavaBeans更加安全。