1. 程式人生 > >一、考慮使用靜態工廠方法替代構造函數

一、考慮使用靜態工廠方法替代構造函數

fault 變化 擁有 def enum 可選 不能 鼓勵 ice

1、何為靜態工廠方法

  靜態工廠方法就是一個返回類實例的靜態方法。比如Boolean的valueof方法:

1 public static final Boolean TRUE = new Boolean(true);
2 public static final Boolean FALSE = new Boolean(false);
3 
4 public static Boolean valueOf(boolean b) {
5     return (b ? TRUE : FALSE);
6 }

2、為什麽使用靜態工廠方法

  和公共構造函數相比,靜態工廠方法有許多的優點,下面進行一一介紹:

  (1)靜態工廠方法是有名字的

    一個類的構造函數的名字都是相同的,為了區分兩個不同的構造函數,只能通過參數類型的順序或個數,這樣,在沒有閱讀參考類文檔的情況下,使用者很可能會錯誤的調用構造方法。

    而靜態工廠方法不同,如果有一個精心選擇的方法名稱,那麽就不會存在上面說的問題,便於程序的閱讀和理解。

    例如,返回一個可能為素數的方法,在未查看文檔的情況下,靜態工廠方法比構造函數更容易理解:

 1 // 構造方法
 2 public BigInteger(int bitLength, int certainty, Random rnd) {
 3     BigInteger prime;
4 5 if (bitLength < 2) 6 throw new ArithmeticException("bitLength < 2"); 7 prime = (bitLength < SMALL_PRIME_THRESHOLD 8 ? smallPrime(bitLength, certainty, rnd) 9 : largePrime(bitLength, certainty, rnd)); 10 signum = 1; 11 mag = prime.mag;
12 } 13 14 15 // 靜態工廠方法 16 public static BigInteger probablePrime(int bitLength, Random rnd) { 17 if (bitLength < 2) 18 throw new ArithmeticException("bitLength < 2"); 19 20 return (bitLength < SMALL_PRIME_THRESHOLD ? 21 smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) : 22 largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd)); 23 }

  (2)靜態工廠方法不用每次調用時候都創建一個新的對象

    靜態工廠方法可以在任何時候對存在類的實例進行控制,在重復調用的時候返回相同的對象,避免創建不必要的重復對象。這樣做可以極大的提高程序的性能,尤其是對那些創建成本非常高的類。比如在Boolean類的valueof方法中,它返回的是預先定義的對象,而這2個對象是靜態不可變的,所以無論在何時調用這個方法,都會返回相同的對象,而不是重新創建對象。

  (3)靜態工廠方法可以返回其子類型對象

    這樣為選擇返回對象提供了很大的靈活性。如java.util.EnumSet類,可以返回EnumSet的子類。

  (4)靜態工廠方法可以根據輸入參數的不同返回不同的類

    返回類型的任何子類都是可以的,返回對象的類可以根據參數不同而發生變化。

    比如java.util.EnumSet,其本身被abstract修飾,無法直接調用其構造函數。但可以調用其靜態方法noneOf來創建對象,並且根據參數返回合適的對象:

 1 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
 2     Enum<?>[] universe = getUniverse(elementType);
 3     if (universe == null)
 4         throw new ClassCastException(elementType + " not an enum");
 5 
 6     if (universe.length <= 64)
 7         return new RegularEnumSet<>(elementType, universe);
 8     else
 9         return new JumboEnumSet<>(elementType, universe);
10 }

    方法返回的2個類的存在對客戶端來講是不可見的,如果在未來版本中淘汰其中1個實例,不會造成任何的影響;同時,添加第三或第四個實現,也不會存在問題。

  (5)靜態工廠方法在編寫包含該方法的類時,返回對象的類可以不存在

    這構成了服務提供者框架的基礎,如jdbc的API。服務提供者框架是提供者實現服務的系統,並且系統使得實現對客戶端可用,從而將客戶端從實現中分離出來。

    服務提供者框架基本組成:

    ① 服務接口:基本組件,表示實現;

    ② 提供者註冊API:基本組件,提供者用來註冊實現;

    ③ 服務訪問API:基本組件,客戶端使用該API獲取服務的實例;允許客戶指定選擇實現的標準,在缺乏標準的情況下,API返回一個默認實現的實例,或者允許客戶通過所有可用的實例進行遍歷;

    ④ 服務提供者接口:可選組件,描述一個生成服務接口實例的工廠對象。

3、靜態工廠方法的缺點

  (1)沒有公共或者被保護的構造方法的類不能被子類化

    如果我們在類中,將構造方法設置為private,只提供靜態工廠方法來構建對象,那麽我們將不能通過繼承來擴展該類。

    但是這也會鼓勵我們使用組合而不是繼承,並且是不可變類。

  (2)程序員很難找到靜態工廠方法

    靜態工廠方法不像構造方法那樣在API文檔中顯得很突出,這樣找到他們就變的非常困難。這樣,我們只能通過將註意力引到類或接口文檔中的靜態方法以及通用的命名約定來減少這個問題。

    靜態工廠方法的常用名稱有:

    • from:接收單個參數並返回此類型的相應實例,如Date d = Date.from(instant);
    • of:接收多個參數並返回該類型的實例,並把他們合並在一起,如Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
    • valueof:from和of更為詳細的替代方式,如BigInteger prime = BigInteger.valueof(Integer.MAX_VALUE);
    • instance或者getInstance:返回一個由其參數描述的實例,返回的實例可能擁有相同的值,如StackWalker luke = StackWalker.getInstance(options);
    • create或者newInstance:和instance或者getInstance類似,不同的是該方法保證每一個調用都返回一個新的實例,如Object newArray = Array.newInstance(classObject, arrayLen);
    • newType:和newInstance類似,但只有在工廠方法在不同類中時,才使用該方法,如BufferedReader br = Files.newBufferedReader(path);
    • type:getType和newType簡潔的替代方式。

總之,靜態工廠方法和構造函數都有各自的用途,但通常靜態工廠方法是更可取的,所以應該避免在未考慮使用靜態工廠方法的基礎上就直接使用公共構造方法。      

一、考慮使用靜態工廠方法替代構造函數