摘要:提供靜態工廠方法而不是公共構造函數既有優點也有缺點。它們不像構造函數那樣在文檔中脫穎而出,因此很難弄清楚如何實例化提供靜態工廠方法而不是構造函數的類。
??類允許客戶端獲取實例的傳統方法是提供公共構造器。還有一種技術應該是每個程序員的工具箱的一部分。一個類可以提供一個公共靜態工廠方法,它僅僅是一個返回類實例的靜態方法。下面是布爾(布爾型的盒裝原語類)的一個簡單示例。這個方法將一個布爾原始值轉換成布爾對象引用:
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
??注意: 靜態工廠方法與設計模式 [Gamma95] 的工廠方法模式不同,在這個Item中描述的靜態工廠方法并不等同于設計模式中的工廠方法模式。
??一個類可以為其客戶提供靜態工廠方法,而不是公共構造函數。提供靜態工廠方法而不是公共構造函數既有優點也有缺點。
??靜態工廠方法的一個優點是,它們是有名稱的,而構造函數的名稱都是一樣的。 如果構造函數的參數本身不能描述返回的對象,那么使用一個精心命名的靜態工廠更容易使用,并且生成的客戶端代碼更容易閱讀。例如BigInteger的一個構造函數:BigInteger(int, int, Random),這個構造函數返回一個BigInteger有可能是一個質數,使用一個精心命名的靜態工廠方法會更容易描述該方法返回的BigInteger類型,比如BigInteger.probablePrime(這是在Java 4 中添加的)。
??一個類只能有一個帶有給定簽名的構造函數。程序員可以通過提供兩個構造函數來繞過這個限制,這些構造函數的參數列表只在參數類型的順序上有所不同。這是個相當壞的主意。這樣使用這個API的用戶永遠無法記住應該使用哪個構造函數,并且最終會調用錯誤的構造函數。使用這些構造函數的人在不閱讀的引用類文檔的情況下是不知道代碼是干什么的。
??因為構造函數有名稱,所以就不會有這個限制,在一個類需要使用多個簽名、多個構造函數的情況下,用靜態工廠方法代替構造函數,并仔細選擇方法的名稱就可以突出構造函數之間的差異了。
??靜態工廠方法的第二個優點是,與構造函數不同,它們不需要在每次被調用時創建一個新對象。 這允許不可變類(第17項)使用預先構造的實例,或者在構建時緩存實例,并重復分發它們,以避免創建不必要的重復對象。Boolean.valueOf(boolean)方法使用了這種方式:它從不創建對象。這種技術類似于享元模式[Gamma95]。如果經常請求等效對象,特別是當它們的創建成本很高時,它可以極大地提高性能。
??靜態工廠方法從重復調用中返回相同的對象的能力允許類在任何時候保持對實例的嚴格控制。這樣做的類被認為是實例控制的。編寫實例控制類的原因有幾個。實例控制允許一個類保證它是一個單例(第3項)或非實例化的(第4項),并且它允許一個不可值的類(第17項)來保證沒有兩個相等的實例存在:當且僅當a==b成立時,a.equals(b)返回true,這是享元模式 [Gamma95] 的基礎,枚舉類型就提供了這種保證。
??靜態工廠方法的第三個優點是,與構造函數不同,它們可以的對象可以是返回類型的任何子類的實例對象。 這使在選擇返回的對象的類時具有很大的靈活性。
??這種靈活性的一個應用是,API可以返回對象,同時又不會使對象的類編程公有的,以這種方式隱藏實現類會使API變得非常簡潔。這種技術適用于基于接口的框架(interface-based frameworks,見第20項),因為在這種框架中,接口為靜態工廠方法提供了自然返回類型。
??在Java 8 之前,接口不能有靜態方法。按照慣例,名為Type的接口的靜態工廠方法被放置在一個不可實例化的名為Types的配套類(noninstantiable companion class)(第4項)中。例如,Java Collections Framework有45個便利實現,分別提供了不可修改的集合、同步集合等等。幾乎所有這些實現都通過靜態工廠方法在一個不可實例化的類(java.util.Collections)中導出。所有返回對象的類都是非公有的。
??現在的Collections Framework API比導出的45個獨立的公有類的那種實現方式要小得多,每種便利的實現都對應一個類。這不僅僅減少了API的數量,還包括概念上的權重:程序猿必須掌握的概念的數量和難度,以便使用API。程序猿知道返回的對象正好有其接口指定的API,因此不需要為實現類去閱讀額外的類文檔。此外,這種工廠方法要求客戶端通過接口而不是實現類來引用返回的對象,這通常是很好的實踐方式(第64項)。
??從Java 8開始,消除了接口不能包含靜態方法的限制,因此通常沒有理由為接口提供不可實例化的伴隨類。許多公共靜態成員應該放在接口本身中。但請注意,可能仍有必要將大量實現代碼放在這些靜態方法后面的多帶帶的包私有類中。這是因為Java 8要求接口的所有靜態成員都是公共的。 Java 9允許私有靜態方法,但靜態字段和靜態成員類的屬性依然是要求是公共的。
??靜態工廠方法的第四個優點是,靜態工廠方法所返回的對象的類可以隨著每次調用而變化,這取決于靜態工廠方法的參數值。 只要返回的類型是聲明的類的子類都是允許的。返回對象的類也可能隨著發行版本的不同而不同。
??在EnumSet類(第36項)中有非公有的構造方法,只有靜態工廠方法。在 OpenJDK 實現中的,它們返回兩個子類之一的實例,具體取決于基礎枚舉類型的大小: 如果它有64個或更少的元素,就像大多數枚舉類型所做的那樣,靜態工廠返回一個RegularEnumSet實例, 它由單個long的支持;如果枚舉類型有65個或更多元素,則工廠將返回一個由長數組支持的JumboEnumSet實例。
??這兩個實現類的存在對于客戶端是不可見的。 如果 RegularEnumSet 不再為小枚舉類型提供性能優勢可以從未來版本中刪除,沒有任何不良影響。 同樣,未來如果證明有利于性能,則可以添加EnumSet的第三或第四個實現。客戶即不知道也不關心他們從工廠中獲取的對象的類型,他們只關心它是EnumSet的一些子類。
??靜態工廠方法的第五個優點是,返回的對象所屬的類,在編寫包含該靜態工廠方法的類時可以不必存在。 這種靈活的靜態工廠方法構成了服務提供者框架(Service Provider Framework)的基礎,例如JDBC(Java數據庫連接,Java Database Connectivity)API。服務提供者框架是提供者實現服務的系統,系統使實現可用于客戶端,將客戶端與實現分離【微服務】。
??服務提供者框架中有三個基本組件:服務接口【提供者】,代表一個實現;提供者注冊API【注冊中心】,提供者用于注冊實現; 以及服務訪問API【消費者】,客戶端使用它來獲取服務的實例。 服務訪問API可以允許客戶端指定用于選擇實現的標準。 如果沒有這樣的標準,API將返回默認實現的實例,或允許客戶端循環遍歷所有可用的實現。 服務訪問API是靈活的靜態工廠,它構成了服務提供者框架的基礎。
??服務提供者框架的可選第四個組件是服務提供者接口,它描述了生成服務接口實例的工廠對象。 在缺少服務提供者接口的情況下,必須反復實例化實現(第65項)。 對于JDBC,Connection扮演服務接口的一部分,DriverManager.registerDriver是提供者注冊API,DriverManager.getConnection是服務訪問API,Driver是服務提供者接口。
??服務提供者框架模式有許多變體。 例如,服務訪問API可以向客戶端返回比提供者提供的服務接口更豐富的服務接口。 這是橋接模式 [Gamma95] 。 依賴注入框架(第5項)可視為強大的服務提供者。 從Java 6開始,該平臺包含一個通用服務提供程序框架java.util.ServiceLoader,因此您不需要(通常不應該)自己編寫(第59項)。 JDBC不使用ServiceLoader,因為前者早于后者。
??靜態工廠方法的主要限制在于,類如果不含公有的或者受保護的構造器,就不能被子類化。 例如:不可能將Collections Framework中的任何方便的實現類子類化。但是這也許會因禍得福,因為它鼓勵程序猿使用組合,而不是繼承(第18項),并且要求必須是不可變的(第17項)。
??靜態工廠方法的第二個缺點是程序員很難找到它們。 它們不像構造函數那樣在API文檔中脫穎而出,因此很難弄清楚如何實例化提供靜態工廠方法而不是構造函數的類。 Javadoc工具有一天可能會引起對靜態工廠方法的注意。 在此期間,您可以通過引起對類或接口文檔中的靜態工廠的注意并遵守常見的命名約定來減少此問題。 以下是靜態工廠方法的一些常用名稱。 這份清單遠非詳盡無遺:
from:一種類型轉換方法,它接受單個參數并返回此類型的相應實例,例如:Date d = Date.from(instant);
of:一種聚合方法,它接受多個參數并返回包含它們的此類型的實例,例如:Set
valueOf:一個更詳細的替代方案,例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance or getInstance:返回由其參數(如果有)描述的實例,但不能說它具有相同的值,例如:StackWalker luke = StackWalker.getInstance(options);
create or newInstance:與instance或getInstance類似,不同之處在于該方法保證每個調用都返回一個新實例,例如:Object newArray = Array.newInstance(classObject, arrayLen);
getType:與getInstance類似,是在工廠方法位于不同的類中時使用它。 Type指的是工廠方法返回的對象類型,例如:FileStore fs = Files.getFileStore(path);
newType:與newInstance類似,是在工廠方法位于不同的類中的時候使用。Type指的是工廠方法方位的對象類型,例如:BufferedReader br = Files.newBufferedReader(path);
type:獲取Type和new Type一個簡明替代的方法,比如:List
??總之,靜態工廠方法和公共構造函數都有它們的用途,理解它們的相對優點是值得的。 通常靜態工廠是優選的,不要在第一反應就是使用構造函數,應當先考慮使用靜態工廠方法。
關注公眾號獲取同步更新
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73876.html
摘要:傳遞給構造器的參數本身就是一個實例,功能方面等同于構造器創建的所有對象。對于同時提供了靜態工廠方法第項和構造器的不可變類,通常可以使用靜態工廠方法而不是構造器,這樣可以經常避免創建不必要的對象。 ??一般來說,最好能重用對象而不是在每次需要的時候就創建一個相同功能的新對象。重用的方式既快速,有流行。如果對象是不可變(immutable)的(第17項),那么就能重復使用它。 ??作為一個...
摘要:一個類可以提供一個公共靜態工廠方法,它僅僅是一第項遇到多個構造器參數時要考慮使用構建器靜態工廠和構造器有個共同的局限性他們都不能很好地擴展到大量的可選參數。 ??本章涉及創建和銷毀對象,包括何時以及如何創建它們,何時以及如何避免創建它們,如何確保它們被及時銷毀,以及如何管理在銷毀之前必須進行的清理操作。 第1項:用靜態工廠方法代替構造器 ??類允許客戶端獲取實例的傳統方法是提供公共構造...
摘要:因此,最好一開始就考慮使用構造器。與使用傳統的重疊構造器模式相比,使用模式的客戶端代碼更易于閱讀和編寫,構建器也比更加安全。 ??靜態工廠和構造器有個共同的局限性:他們都不能很好地擴展到大量的可選參數。考慮用一個類表示包裝食品外面顯示的營養成分標簽。這些標簽中有幾個域是必需的:每份的含量、每罐的含量以及每份的卡路里,還有超過20個可選域:總脂肪、飽和脂肪量、轉化脂肪、膽固醇、鈉等等。大...
摘要:推薦序前言致謝第一章引言第二章創建和銷毀對象第項用靜態工廠方法代替構造器第項遇到多個構造器參數時要考慮使用構建器第項用私有構造器或者枚舉類型強化屬性第項通過私有構造器強化不可實例化的能力第項優先考慮依賴注入來引用資源第項避免創建不必要的對象 推薦序 前言 致謝 第一章 引言 第二章 創建和銷毀對象 第1項:用靜態工廠方法代替構造器 第2項:遇到多個構造器參數時要考慮使用構建器 第...
摘要:本章中的大部分內容適用于構造函數和方法。第項其他方法優先于序列化第項謹慎地實現接口第項考慮使用自定義的序列化形式第項保護性地編寫方法第項對于實例控制,枚舉類型優先于第項考慮用序列化代理代替序列化實例附錄與第版中項目的對應關系參考文獻 effective-java-third-edition 介紹 Effective Java 第三版全文翻譯,純屬個人業余翻譯,不合理的地方,望指正,感激...
閱讀 1368·2021-09-13 10:25
閱讀 552·2019-08-30 15:53
閱讀 2265·2019-08-30 15:44
閱讀 2026·2019-08-29 17:20
閱讀 1594·2019-08-29 16:36
閱讀 1795·2019-08-29 14:10
閱讀 1785·2019-08-29 12:44
閱讀 1166·2019-08-23 14:13