高效Java第一條考慮用靜態工廠代替構造函數

獲得類的實例:
1.提供一個公有的構造函數;
2.提供一個公有的靜態工廠方法,該方法只是一個返回類的實例的靜態方法。


靜態工廠方法與設計模式中的工廠方法模式不同。

提供靜態工廠方法的優勢——靜態工廠方法與構造函數不同的第一大優勢在于,它們有名稱

如果構造函數的參數本身沒有確切地描述正被返回的對象,那么具有適當名稱的靜態工廠會更容易被使用,產生的客戶端代碼也更易于閱讀。

BigInteger(int,int,Random)返回的BigInteger可能為素數,如果用名為BigInteger.probablePrime的靜態方法來表示,會更為清楚。
一個類只能有一個帶有指定簽名的構造函數。
提供兩個構造函數,它們的參數列表只在參數類型的順序上有所不同。這不是好主意。用戶永遠也記不住該用哪個構造函數,因此會經常調用錯誤的構造函數。并且,讀調用這些構造函數的代碼是有難度的。

當一個類需要多個帶有相同簽名的構造函數時,可以用靜態工廠方法代替構造函數,通過慎重地選擇名稱可以突出它們之間的區別。

提供靜態工廠方法的優勢——靜態工廠方法與構造函數不同的第二大優勢在于,不必在每次調用它們的時候都創建一個新的對象

可以避免創建不必要的重復對象。
Booelan.valueOf(booelan)方法從不創建對象。
類似于Flyweight模式。
如果程序經常請求創建相同的對象,并且創建對象的代價很好,則這項技術可以極大地提升性能。

實例受控的類:靜態工廠方法能夠為重復的調用返回相同的對象,這樣有助于類總能嚴格控制在某個時刻那些實例應該存在。

提供靜態工廠方法的優勢——靜態工廠方法與構造函數不同的第三大優勢在于,它們可以返回原返回類型的任何子類型的對象

API可以返回對象,又不會使對象的類變成公有的。以這種方式隱藏實現類會使API變得非常簡潔。
這項技術適用于基于接口的框架,接口為靜態工廠方法提供了自然返回類型。
接口不能有靜態方法,因此按照慣例,接口Type的靜態工廠方法被放在一個名為Types的不可實例化的類中。

Collections Framework API比導出32個獨立公有類的實現方式要小得多,每種便利實現都對應一個類。這不僅是API數量上減少,也是概念意義上的減少。
被返回的對象是由相關的接口精確指定的。
使用這種靜態工廠方法時,甚至要求客戶端通過接口來引用被返回的對象,而不是通過它的實現類來引用被返回的對象,這是一種良好的習慣。

公有的靜態工廠方法所返回的對象的類不僅可以是非公有的,而且該類還可以隨著每次調用而發生變化,這取決于靜態工廠方法的參數值。只要是已聲明的返回類型的子類型,都是允許的。為了提升軟件的可維護性和性能,返回對象的類也可能隨著發行版本的不同而不同。

JDK5引入的java.util.EnumSet沒有公有構造函數,只有靜態工廠方法。它返回兩種實現類之一,取決于底層枚舉類型的大小:如果元素有64個或者更少,靜態工廠方法就會返回一個RegularEnumSet實例,用單個long進行支持;如果枚舉類型有65個或者更多元素,工廠就返回JumboEnumSet實例,用long數組進行支持。
這兩個實現類對客戶來說是不可見的。如果RegularEnumSet不能再給小的枚舉類型提供性能優勢,就可能從未來的發行版本中被刪除,這不會造成不良的影響。如果事實證明對性能有好處,在未來的發行版本中可以添加其他的EnumSet實現。客戶永遠不知道也不關心他們從工廠方法中得到的對象的類,他們只需要知道它是EnumSet的某個子類即可。

靜態工廠方法返回的對象所屬的類,在編寫包含該靜態工廠方法的類時可以不必存在。這種靈活的靜態工廠方法構成了服務提供者框架的基礎(JDBC).

提供靜態工廠方法的優勢——靜態工廠方法與構造函數不同的第四大優勢在于,在創建參數化類型實例的時候,它們使代碼變得更加簡潔。

在調用參數化類的構造函數時,即使類型參數很明顯,也必須指明。也就是說要接連兩次提供類型參數:

使用靜態工廠方法,編譯器可以推導出類型參數(類型推導)。

JDK8竟然還會警告。

靜態工廠方法的主要缺點在于:類如果不含公有的或者受保護的構造函數,就不能被子類化。

對于公有的靜態工廠所返回的非公有類,也是如此。
鼓勵程序員使用復合,而不是繼承。

靜態工廠方法的第二個缺點在于,它們與其他的靜態方法沒有任何區別。

在API文檔中,它們沒有像構造函數那樣在API文檔中明確地標識出來。
對于提供了靜態工廠方法而不是構造函數的類來說,要想查明如何實例化一個類,是非常困難的。

靜態工廠方法的慣用名稱

valueOf——該方法返回的實例與它的參數具有相同的值。實際上是類型轉換方法。
of——valueOf的簡潔替代,在EnumSet中使用并流行起來。
getInstance——返回的實例是通過方法的參數來描述的,但是不能夠說與參數具有同樣的值。對于單例來說,該方法沒有參數,并返回唯一的實例。
newInstance——與getInstance一樣,但newInstance確保返回的每個實例都與所有其他實例不同。
getType——在工廠方法處于不同的類中的時候使用。Type表示工廠方法所返回的對象類型。
newType——在工廠方法處于不同的類中的時候使用。Type表示工廠方法所返回的對象類型。

服務提供者框架概念

服務提供者框架:多個服務提供者實現一個服務,系統為服務提供者的客戶提供多個實現,并把他們從多個實現中解耦出來。

服務提供者三個組件

服務接口:提供者實現
提供者注冊API:系統用來注冊實現,讓客戶訪問它們
服務訪問API:客戶端用來獲取服務的實例

服務訪問API允許但是不要求客戶端指定某個選擇提供者的條件,如果沒有指定,則返回默認的實現。服務訪問API是靈活的靜態工廠,它構成了服務提供者框架的基礎。

服務提供者可選組件

服務提供者接口,這些提供者負責創建其服務實現的實例。如果沒有服務提供者接口,實現就按照類名稱注冊,并通過反射方式進行實例化。

服務提供者例子——JDBC

Connection服務接口
DriverManager.registerDriver是提供者注冊API
DriverManager.getConnection是服務訪問API
Driver是服務提供者接口

編寫實例受控的類的原因

實例受控使得類可以確保它是一個單例或是不可實例化的。
實例受控使得不可變類可以確保不會存在兩個相等的實例,即當且僅當a==b的時候才有a.equals(b)true。如果類保證了這一點,它的客戶端就可以使用==操作符來代替equals(Object)方法,這樣可以提升性能。

總結

靜態工廠通常更加適合,因此切忌第一反應就是提供公有的構造函數,而不優先考慮靜態工廠。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容