天天看點

Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

2.2 無需在每次調用時建立新對象

這使得不可變類使用事先構造好的執行個體,或在構造執行個體時緩存執行個體,重複配置設定以避免建立不必要的重複對象。Boolean.valueOf(boolean) 方法:它從不建立對象。

Boolean類中該方法将 boolean 基本類型值轉換為一個 Boolean 對象引用

傳回一個Boolean表示指定執行個體boolean的值。 如果指定的boolean值是true ,則此方法傳回Boolean.TRUE ; 如果是false ,這個方法傳回Boolean.FALSE 。 如果并不需要一個新的Boolean 執行個體,該方法一般應優于構造器中使用Boolean(boolean) ,因為此方法可能産生顯著的更好的空間和時間性能

Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

這類似于享元模式。如果經常請求相同對象,特别是建立對象代價高時,可以極大提高性能。

靜态工廠方法在重複調用下傳回相同對象,這樣類能嚴格控制存在的執行個體。這樣的類稱為執行個體受控的類。編寫執行個體受控的類有幾個原因。

允許一個類來保證它是一個單例或不可執行個體化的。同時,它允許一個不可變的值類保證不存在兩個相同的執行個體:

a.equals(b) 當且僅當 a==b。這是享元模式的基礎。枚舉類型提供了這種保證。

2.3 擷取傳回類型的任何子類的對象

這為選擇傳回對象的類提供靈活性。

這種靈活性的一個應用是 API 可以在public其類的情況下傳回對象。以這種方式隐藏實作類會形成一個非常緊湊的 API。這适用于基于接口的架構,其中接口為靜态工廠方法提供了自然的傳回類型。

Java 8 前,接口不能有靜态方法。按照慣例,一個名為 Type 的接口的靜态工廠方法被放在一個名為 Types 的不可執行個體化的伴生類。例如,Java 的 Collections 架構有 45 個接口實用工具實作,提供了不可修改的集合、同步集合等。幾乎所有這些實作都是通過一個非執行個體化類(java.util.Collections)中的靜态工廠方法導出的。傳回對象的類都是非public的。

現在的Collections 架構 API 比它導出 45 個獨立的public類小得多,每個公共類對應一個友善的實作。減少的不僅僅是 API 的數量,還有概念上的減少:程式員為了使用 API 必須掌握的概念的數量和難度。程式員知道傳回的對象是由相關的接口精确指定的,是以不需閱讀額外的類文檔。

使用這種靜态工廠方法需要用戶端通過接口而不是實作類引用傳回的對象,這通常是很好的做法。

Java 8 取消了接口不能包含靜态方法的限制,是以通常沒有理由為接口提供不可執行個體化的伴生類。許多公共靜态成員應該放在接口本身中,而不是放在類中。但仍有必要将這些靜态方法背後的大部分實作代碼放到單獨的包私有類中。因為 Java 8 要求接口的所有靜态成員都是公共的,而 Java 9 允許接口有私有的靜态方法,但是靜态字段和靜态成員類仍然需是public

A fourth advantage of static factories is that the class of the returned object can vary from call to call as a function of the input parameters. Any subtype of the declared return type is permissible. The class of the returned object can also vary from release to release.

2.4 傳回對象的類可以随調用的不同而變化

這當然取決于輸入參數不同。隻要是已聲明的傳回類型的子類型都是允許的。傳回對象的類也可以因版本而異。

  • EnumSet 類沒有public構造器
  • Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結
  • 隻有靜态工廠。在 OpenJDK 中,它們傳回兩種子類之一的一個執行個體,這取決于底層 enum 類型的大小:

如果它有 64 個或更少的元素,就像大多數 enum 類型一樣,靜态工廠傳回一個 long 類型的 RegularEnumSet 執行個體

如果 enum 類型有 65 個或更多的元素,工廠将傳回一個由 long[] 類型的 JumboEnumSet 執行個體。

用戶端看不到這兩個實作類的存在。如果 RegularEnumSet 不再為小型 enum 類型提供性能優勢,它可能會在未來的版本中被删除,而不會産生任何負面影響。類似地,如果證明 EnumSet 有益于性能,未來的版本可以添加第三或第四個 EnumSet 實作。用戶端既不知道也不關心從工廠傳回的對象的類;它們隻關心它是 EnumSet 的某個子類。

2.5 當編寫包含靜态工廠方法的類時,傳回對象的類可以不存在

這種靈活的靜态工廠方法構成了服務提供者架構(Service Provider Framework,SPF)的基礎,比如 JDBC API。

SPF系統

多個提供者實作一個服務,該系統使用戶端可以使用這些實作,進而将用戶端與實作分離。

SPF有三個基本元件

  • 代表實作的服務接口
  • 提供者注冊 API,提供者使用它來注冊實作

服務通路 API,用戶端使用它來擷取服務的執行個體。服務通路 API 允許用戶端指定選擇實作的條件。在沒有這些條件的情況下,API 傳回一個預設實作的執行個體,或者允許用戶端循環使用所有可用的實作。服務通路 API 是靈活的靜态工廠,它構成了服務提供者架構的基礎。

SPF第四個可選元件是服務提供者接口,它描述産生服務接口執行個體的工廠對象。在沒有服務提供者接口的情況下,必須以反射的方式執行個體化實作。

JDBC案例

  1. Connection扮演服務接口的一部分
  2. DriverManager.registerDriver

    是提供者注冊 API
  3. DriverManager.getConnection

    是服務通路 API
  4. Driver是服務提供者接口

SPF模式有許多變體。例如,服務通路 API 可以向用戶端傳回比提供者提供的更豐富的服務接口,這就是橋接模式 。依賴注入(DI)架構就可以看成是強大的服務提供者。從Java 6開始,平台就提供了一個通用服務提供者架構 Java.util.ServiceLoader,是以你不需要,通常也不應該自己寫。JDBC 不使用 ServiceLoader,因為前者比後者早!

3 缺點

  • 工廠類的職責相對過重,增加新的産品
  • 需要修改工廠類的判斷邏輯,違背開閉原則

3.1 僅提供靜态工廠方法的主要局限是,沒有public或protected構造器的類不能被繼承

例如,不可能在集合架構中子類化任何便利的實作類。這可能是一種因禍得福的做法,因為它鼓勵程式員使用組合而不是繼承,這對于不可變類型是必須的。

3.2 程式員很難找到它們

它們在 API 文檔中不像構造器吸睛,是以很難弄清楚如何執行個體化一個隻提供靜态工廠方法而沒有構造器的類。Javadoc 工具總有一天會關注到靜态工廠方法。

通過在類或接口文檔中多關注靜态工廠方法,遵守通用命名約定的方式來減少這個困擾。

下面是一些靜态工廠方法的習慣命名。

from,類型轉換方法,接收單個參數并傳回該類型的相應執行個體

Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

of,聚合方法,接受多個參數并傳回一個執行個體

Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

valueOf,比 from 和 of 但更繁瑣的一種替代方法

Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

instance 或 getInstance,傳回一個執行個體,該執行個體由其參數(如果有的話)描述,但不和參數具有相同的值

StackWalker luke = StackWalker.getInstance(options);      

create 或 newInstance,與 instance 或 getInstance 類似,隻是該方法保證每個調用都傳回一個新執行個體

Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

getType,類似于 getInstance,但如果工廠方法位于不同的類中,則使用此方法。其類型是工廠方法傳回的對象類型,例如:

FileStore fs = Files.getFileStore(path);      

newType,與 newInstance 類似,但是如果工廠方法在不同的類中使用。類型是工廠方法傳回的對象類型,例如:

Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

type,一個用來替代 getType 和 newType 的比較簡單的方式

List<Complaint> litany = Collections.list(legacyLitany);      
Java架構師教你寫代碼(一) - 使用靜态工廠方法(簡單工廠)替代構造器(下)3 缺點總結

适用場景

  • 工廠類負責建立的對象比較少
  • 用戶端(應用層)隻知道傳入工廠類的參數,對于如何建立對象(邏輯)不關心

總結

靜态工廠方法和public構造器 各有千秋,我們需要了解它們各自的優點。通常靜态工廠更可取,是以切忌不考慮靜态工廠就提供public構造器。

參考

  • effective java