標籤:
Effective Java
建立和銷毀對象---考慮用靜態Factory 方法代替構造器
構造器是建立一個對象執行個體最基本也最通用的方法,大部分開發人員在使用某個class的時候,首先需要考慮的就是如何構造和初始化一個對象樣本,而構造的方式首先考慮到的就是通過建構函式來完成,因此在看javadoc中的文檔時首先關注的函數也是構造器。所以對於類而言,我們為了獲得一個類的執行個體對象,通常情況下會提供一個公有的(public) 的構造器。當然除了這種方法以外,我們還可以通過給類提供一個public的靜態Factory 方法(static factory method)的方式來完成,讓它返回一個類的執行個體。如:
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
這個樣本將boolean基本類型值轉換成一個Boolean對象的引用。(需要注意的是這裡的靜態Factory 方法與設計模式中所說的Factory 方法模式不同)那麼,在Effective Java的首篇中,我們來看看靜態Factory 方法相比於構造器帶來了哪些優勢:1. 靜態Factory 方法可以有不同的名稱,代碼更清楚,更易讀。在架構設計中,針對某些工具類通常會考慮Null 物件以辨別該對象是否已經被初始化。構造器的命名都一致,一個類只能有一個指定簽名的構造器。當一個類需要提供多個構造器時,通常只是通過不同的形參類型的順序加以區分,但其函數名還是相同的,無法提供較高的區分度。那麼,當一個類需要多個帶有相同簽名的構造器時,不妨考慮用靜態Factory 方法代替構造器,並且謹慎地選擇名稱以便突出它們之間的區別。這樣該靜態Factory 方法有名稱,通過賦予有意義的名稱,使用該方法的程式員可以清晰的知道該方法的含義。(方法的簽名(signature)由它的名稱和所有參數的傳回型別組成,而不包括它的傳回型別)2. 靜態Factory 方法不必在每次調用它們的時候都建立一個新對象。比如你可以在靜態Factory 方法裡限定建立該對象的個數,當超出規定的個數時,返回緩衝裡的對象。情境:調用構造器時每次都建立新對象。這一點很顯然,我們知道Singleton其實也提供一個靜態Factory 方法擷取執行個體。除此之外,可以用==代替equals()方法,達到效能的提升。3. 靜態Factory 方法可以返回原傳回型別的任何子類型的對象。情境:構造器只能返回當前類的執行個體,無法返回子類的執行個體。這個優點的確很有用,能夠充分利用多態,使代碼更具有可擴充性。設計模式中的Factory 方法也有體現,可根據入參type傳回型別為Types的不同子類執行個體。
public Types getType(String type) {...}
這樣我們在選擇返回對象的類時就有更大的靈活性,這種靈活性的應用就是API可以返回對象,同時又不會使對象的類變成公有的。以這種方式隱藏實作類別會使API變得簡潔。這項技術適合於基於介面的架構(interface-based framework)。這種面向介面的編程也提高了軟體的可維護性和效能。4. 靜態Factory 方法在建立參數化型別執行個體的時候使代碼變得更加簡潔。情境: Java的泛型類在執行個體化時,還是需要寫兩次型別參數,非常冗長。靜態Factory 方法可以幫你改善這種情況:舉個例子,假設在HashMap類中提供如下靜態Factory 方法:
public static <k, v=""> HashMap<k, v=""> newInstance(){ return new HashMap<k, v="">(); }
那麼,調用時應該是這樣的:
HashMap<stirng, list<string="">> m = HashMap.newInstance();
而在調用參數化的構造器時,則通常需要這樣做,顯得比較繁瑣。
HashMap<string, list<string="">> m = new HashMap<string, list<string="">>();
另外值得一提的是,靜態Factory 方法返回的對象所屬的類,在編寫包含該靜態Factory 方法的類時不必存在。這種靈活的靜態Factory 方法構成了服務提供者架構(Service Provider Framework)的基礎,例如JDBC的API。所謂服務提供者架構是指這樣的一個系統:多個服務提供者實現同一個服務,由架構(即系統)來負責為用戶端提供這種服務的多個實現並將這些實現解耦。服務提供者架構有三個主要的組件:1.服務介面(Service interface),這是提供者實現的。2.提供者註冊API(Provider Registration API),這是系統用來註冊實現,讓用戶端訪問它們的。3.服務訪問API(Service Access API),這是用戶端擷取服務執行個體用的。另外還有第四個可選介面,服務提供者介面(Service Provider Interface),負責建立其服務實現的執行個體。若沒有服務提供者介面,實現就按照類名稱進行註冊,並通過反射方式進行執行個體化。對JDBC而言,Connection就是它的服務介面,DriverManager.registerDriver就是它的提供者註冊API,DriverManager.getConnection是服務訪問API,Driver就是服務提供者介面。
如提供了兩個靜態Factory 方法empty和preallocate(含義,重新分配;再指派)用於分別建立一個Null 物件和一個帶有指定分配空間的String對象。從使用方式來看,這些靜態方法確實提供了有使用者很容易就可以判斷出它們的作用和應用情境,而不必在一組重載的構造器中去搜尋每一個建構函式及其參數列表,以找出適合當前情境的建構函式。
從效率方面來講,由於提供了唯一的靜態Null 物件,當判讀對象執行個體是否為空白時(isEmpty),直接使用預製靜態Null 物件(emptyString)的地址與當前對象進行比較,如果是同一地址,即可確認當前執行個體為空白對象了。對於preallocate函數,顧名思義,該函數預分配了指定大小的記憶體空間,後面在使用該String執行個體時,不必擔心賦值或追加的字元過多而導致頻繁的realloc(重新分配記憶體)等操作。2.不必在每次調用它們的時候建立一個新的對象。還是基於上面的代碼執行個體,由於所有的Null 物件都共用同一個靜態Null 物件,這樣也節省了更多的記憶體開銷,如果是strEmpty2方式構造出的Null 物件,在執行比較等操作時會帶來更多的效率開銷。事實上,Java在String對象的實現中,使用了常量資源集區也是基於了同樣的最佳化策略。該優勢同樣適用於單一實例模式。3.可以返回原傳回型別的任何子類型。在Java Collections Framework的集合介面中,提供了大量的靜態方法返回集合介面類型的實作類別型,如Collections.subList()、Collections.unmodifiableList()等。返回的介面是明確的,然而針對具體的實作類別,函數的使用者並不也無需知曉。這樣不僅極大的減少了匯出類的數量,而且在今後如果發現某個子類的實現效率較低或者發現更好的資料結構和演算法來替換當前實現子類時,對於集合介面的使用者來說,不會帶來任何的影響。本書在例子中提到EnumSet是通過靜態Factory 方法返回對象執行個體的,沒有提供任何建構函式,其內部在返回實作類別時做了一個最佳化,即如果枚舉的數量小於64,該Factory 方法將返回一個經過特殊最佳化的實作類別執行個體(RegularEnumSet),其內部使用long(64bits在Java中)中的不同位來表示不同的枚舉值。如果枚舉的數量大於64,將使用long的數組作為底層支撐。然而這些內部實作類別的最佳化對於使用者來說是透明的。4.在建立參數化型別執行個體的時候,它們使代碼變得更加簡潔。Map<String,String> m = new HashMap<String,String>(); 由於Java在建構函式的調用中無法進行類型的推演,因此也就無法通過構造器的參數類型來執行個體化指定型別參數的執行個體化對象。然而通過靜態Factory 方法則可以利用參數類型推演的優勢,避免了型別參數在一次聲明中被多次重寫所帶來的煩憂,見如下代碼:
只需五分鐘 讀完 Effective Java