1. 程式人生 > >Effective Java 第三版——1. 考慮使用靜態工廠方法替代構造方法

Effective Java 第三版——1. 考慮使用靜態工廠方法替代構造方法

plain 額外 body image 單獨 oba car 翻譯 一個

Tips
《Effective Java, Third Edition》一書英文版已經出版,這本書的第二版想必很多人都讀過,號稱Java四大名著之一,不過第二版2009年出版,到現在已經將近8年的時間,但隨著Java 6,7,8,甚至9的發布,Java語言發生了深刻的變化。
在這裏第一時間翻譯成中文版。供大家學習分享之用。

技術分享圖片

條目1. 考慮使用靜態工廠方法替代構造方法

一個類允許客戶端獲取其實例的傳統方式是提供一個公共構造方法。 其實還有另一種技術應該成為每個程序員工具箱的一部分。 一個類可以提供一個公共靜態工廠方法,它只是一個返回類實例的靜態方法。 下面是一個Boolean簡單的例子(boolean

基本類型的包裝類)。 此方法將boolean基本類型轉換為Boolean對象引用:

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

註意,靜態工廠方法與設計模式中的工廠方法模式不同[Gamma95]。本條目中描述的靜態工廠方法在設計模式中沒有直接的等價。

類可以為其客戶端提供靜態工廠方法,而不是公共構造方法。提供靜態工廠方法而不是公共構造方法有優點也有缺點。

靜態工廠方法的一個優點是,不像構造方法,它們是有名字的。 如果構造方法的參數本身並不描述被返回的對象,則具有精心選擇名稱的靜態工廠更易於使用,並且生成的客戶端代碼更易於閱讀。 例如,返回一個可能為素數的BigInteger

的構造方法BigInteger(int,int,Random)可以更好地表示為名為BigInteger.probablePrime的靜態工廠方法。 (這個方法是在Java 1.4中添加的。)

一個類只能有一個給定簽名的構造方法。 程序員知道通過提供兩個構造方法來解決這個限制,這兩個構造方法的參數列表只有它們的參數類型的順序不同。 這是一個非常糟糕的主意。 這樣的API用戶將永遠不會記得哪個構造方法是哪個,最終會錯誤地調用。 閱讀使用這些構造方法的代碼的人只有在參考類文檔的情況下才知道代碼的作用。

因為他們有名字,所以靜態工廠方法不會受到上面討論中的限制。在類中似乎需要具有相同簽名的多個構造方法的情況下,用靜態工廠方法替換構造方法,並仔細選擇名稱來突出它們的差異。

靜態工廠方法的第二個優點是,與構造方法不同,它們不需要每次調用時都創建一個新對象。這允許不可變的類(條目17)使用預先構建的實例,或者在構造時緩存實例,並反復分配它們以避免創建不必要的重復對象。boolean.valueof(boolean)方法說明了這種方法:它從不創建對象。這種技術類似於Flyweight模式[Gamma95]。如果經常請求等價對象,那麽它可以極大地提高性能,特別是如果在創建它們非常昂貴的情況下。

靜態工廠方法從重復調用返回相同對象的能力允許類保持在任何時候存在的實例的嚴格控制。這樣做的類被稱為實例控制( instance-controlled)。編寫實例控制類的原因有很多。實例控制允許一個類來保證它是一個單例(3)項或不可實例化的(條目4)。同時,它允許一個不可變的值類(條目17)保證不存在兩個相同的實例:當且僅當a== ba.equals(b)。這是享元模式的基礎[Gamma95]。Enum類型(條目34)提供了這個保證。

靜態工廠方法的第三個優點是,與構造方法不同,它們可以返回其返回類型的任何子類型的對象。 這為你在選擇返回對象的類時提供了很大的靈活性。

這種靈活性的一個應用是API可以返回對象而不需要公開它的類。 以這種方式隱藏實現類會使 API非常緊湊I。 這種技術適用於基於接口的框架(條目20),其中接口為靜態工廠方法提供自然返回類型。

在Java 8之前,接口不能有靜態方法。根據約定,一個名為Type的接口的靜態工廠方法被放入一個非實例化的夥伴類(companion class)(條目4)Types類中。例如,Java集合框架有45個接口的實用工具實現,提供不可修改的集合、同步集合等等。幾乎所有這些實現都是通過靜態工廠方法在一個非實例類(java .util. collections)中導出的。返回對象的類都是非公開的。

Collections框架API的規模要比它之前輸出的45個單獨的公共類要小得多,每個類有個便利類的實現。不僅是API的大部分減少了,還包括概念上的權重:程序員必須掌握的概念的數量和難度,才能使用API。程序員知道返回的對象恰好有其接口指定的API,因此不需要為實現類讀閱讀額外的類文檔。此外,使用這種靜態工廠方法需要客戶端通過接口而不是實現類來引用返回的對象,這通常是良好的實踐(條目64)。

從Java 8開始,接口不能包含靜態方法的限制被取消了,所以通常沒有理由為接口提供一個不可實例化的伴隨類。 很多公開的靜態成員應該放在這個接口本身。 但是,請註意,將這些靜態方法的大部分實現代碼放在單獨的包私有類中仍然是必要的。 這是因為Java 8要求所有接口的靜態成員都是公共的。 Java 9允許私有靜態方法,但靜態字段和靜態成員類仍然需要公開。

靜態工廠的第四個優點是返回對象的類可以根據輸入參數的不同而不同。 聲明的返回類型的任何子類都是允許的。 返回對象的類也可以隨每次發布而不同。

EnumSet類(條目 36)沒有公共構造方法,只有靜態工廠。 在OpenJDK實現中,它們根據底層枚舉類型的大小返回兩個子類中的一個的實例:如果大多數枚舉類型具有64個或更少的元素,靜態工廠將返回一個RegularEnumSet實例, 返回一個long類型;如果枚舉類型具有六十五個或更多元素,則工廠將返回一個JumboEnumSet實例,返回一個long類型的數組。

這兩個實現類的存在對於客戶是不可見的。 如果RegularEnumSet不再為小枚舉類型提供性能優勢,則可以在未來版本中將其淘汰,而不會產生任何不良影響。 同樣,未來的版本可能會添加EnumSet的第三個或第四個實現,如果它證明有利於性能。 客戶既不知道也不關心他們從工廠返回的對象的類別; 他們只關心它是EnumSet的一些子類。

靜態工廠的第5個優點是,在編寫包含該方法的類時,返回的對象的類不需要存在。這種靈活的靜態工廠方法構成了服務提供者框架的基礎,比如Java數據庫連接API(JDBC)。服務提供者框架是提供者實現服務的系統,並且系統使得實現對客戶端可用,從而將客戶端從實現中分離出來。

服務提供者框架中有三個基本組:服務接口,它表示實現;提供者註冊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框架中不可能將任何方便實現類子類化。可以說,這可能是因禍得福,因為它鼓勵程序員使用組合而不是繼承(條目18),並且是不可變類型(條目17)。

靜態工廠方法的第二個缺點是,程序員很難找到它們。它們不像構造方法那樣在API文檔中突出,因此很難找出如何實例化一個提供靜態工廠方法而不是構造方法的類。Javadoc工具可能有一天會引起對靜態工廠方法的註意。與此同時,可以通過將註意力吸引到類或接口文檔中的靜態工廠以及遵守通用的命名約定來減少這個問題。下面是一些靜態工廠方法的常用名稱。以下清單並非完整:

  • from——A類型轉換方法,它接受單個參數並返回此類型的相應實例,例如:Date d = Date.from(instant);
  • of——一個聚合方法,接受多個參數並返回該類型的實例,並把他們合並在一起,例如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf——from和to更為詳細的替代 方式,例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance或getinstance——返回一個由其參數(如果有的話)描述的實例,但不能說它具有相同的值,例如:StackWalker luke = StackWalker.getInstance(options);
  • create 或 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—— getType 和 newType簡潔的替代方式,例如:List<Complaint> litany = Collections.list(legacyLitany);

總之,靜態工廠方法和公共構造方法都有它們的用途,並且了解它們的相對優點是值得的。通常,靜態工廠更可取,因此避免在沒有考慮靜態工廠的情況下提供公共構造方法。

Effective Java 第三版——1. 考慮使用靜態工廠方法替代構造方法