第一條建議:考慮用靜態工廠方法代替構造方法——《Effective Java》讀書筆記
《Effective Java》讀書筆記系列文章用於整理和記錄《Effective Java》一書的筆記,一來方便日後查閱和複習,二來與大夥共同學習與分享,並非商用,請大家尊重智慧財產權:smile:。
小盆友並未一成不變的將書本的觀點 cv 到這篇博文中,而是加上自己的一些 “不太成熟” 的見解,將書中的觀點轉為自己所認知的理解來進行記錄。話不多說,開始今天的分享。
二、第一條建議
考慮用靜態工廠方法代替構造方法
三、解釋含義
對於建立一個類的例項,一般我們都會考慮通過該類的 公有建構函式 建立一個例項,這是我們平常最為常見的做法。但是我們或許還可以考慮另外一種方法,就是 靜態工廠方法 來建立我們所需的物件。
舉個例子,我們建立一個 Boolean 物件,以兩種方式建立,則如下程式碼
// 第一種:公有方法建立 Boolean b1 = new Boolean(true); // 第二種:靜態工廠方法建立 Boolean b2 = Boolean.valueOf(true); 複製程式碼
四、使用 “靜態工廠方法” 有什麼好處
(1)靜態工廠方法有屬於自己的方法名稱,而建構函式的名稱永遠只是類的名稱,即使有n個建構函式也是一樣的名稱。
在大學時聽到老師們說的最多一句話是 “程式碼是先給人看,在給機器執行” ,對於機器來說,只要邏輯是正確的,屬性名、函式名對他們來說都是無意義的,而 可讀的程式碼 對維護和迭代來說確實至關重要的。
舉個例子,如下兩段程式碼,其實功能是一樣的,而在 沒有註釋的情況下(程式設計師都不喜歡寫註釋:joy:)或是迅速查閱 API 時 ,建構函式此時的 描述能力 明顯弱於靜態工廠方法,通過靜態工廠方法的名稱我們知道這個函式是 可能返回素數 ,而且還方便我們記憶,畢竟記一串引數型別和順序是很難的,而一個有意義的名稱就很容易了。
// 建構函式 public BigInteger(int bitLength, int certainty, Random random) // 靜態工廠方法 public static BigInteger probablePrime(int bitLength, Random random) { return new BigInteger(bitLength, 100, random); } 複製程式碼
“靜態工廠方法” 優勢小節:
- 程式碼可讀性強,便於迅速查詢和定位函式;
- 方便記憶;
(2)可以避免每次都建立一個新的物件
這一好處,其實我們很多人都在使用著,因為它提供了我們程式設計中一直提到的一個詞 “複用” ,而複用帶來的 直接好處 是 記憶體的節省和效能的提升 (還有其他的連鎖好處)。
而且,我們可以控制該類在系統中存在的例項個數,而帶來的好處是我們可以直接使用 ==
代替 equal
來判斷兩個例項是否是相等,達到 效能的提升 。
舉個例子,如下程式碼,我們最開始使用的 Boolean 的例子中就已經體現了,靜態工廠方法 valueOf
每次都是返回同一物件,而對於這種 不可變內容 的類來說,更為節省空間。而通過 公有構造方法 建立,每次都是新的例項,所以只能通過 equals
來比較。
// 公有構造方法建立 Boolean b1 = new Boolean(true); Boolean b2 = new Boolean(true); // 靜態工廠方法建立 Boolean b3 = Boolean.valueOf(true); // 靜態工廠方法建立 Boolean b4 = Boolean.valueOf(true); // 輸出值 System.out.println("b1 = " + b1); System.out.println("b2 = " + b2); System.out.println("b3 = " + b3); System.out.println("b4 = " + b4); // 比較物件(注意這裡不是比較值,而是物件) System.out.println("b1 == b2 --->" + (b1 == b2)); System.out.println("b1 == b3 --->" + (b1 == b3)); System.out.println("b3 == b4 --->" + (b3 == b4)); // 比較值 System.out.println("b1.equals(b2) --->" + (b3.equals(b4))); System.out.println("b1.equals(b3) --->" + (b1.equals(b3))); System.out.println("b3.equals(b4) --->" + (b3.equals(b4))); 複製程式碼
執行結果:

(3)返回任何其子型別的物件
看到書中提及的這個好處,蹦到我腦海中的是兩個詞: “多型” 和 “面向介面程式設計” 。我們藉助下面這張小盆友靈魂手繪的圖來講解:

各個部分說明:
- Tree 是一個介面(個人覺得某些情況下可以是抽象類);
- A、B、C 為 Tree 的具體實現類;
- Factory 為我們所說的 靜態工廠方法 所存在的類;
- Client 則為 API 的呼叫者;
“面向介面程式設計”在這裡的好處是,遮蔽了具體的實現類(此處的A、B、C)。如果將 A、B、C 三個具體實現類的可訪問的作用域限制為 非公有的 ,使用者獲得的只能是一個 Tree 型別 的介面,則可以達到 剔除對使用者來說無用的方法的作用 ,最終達到 API簡潔 的效果。畢竟使用者只關心他們能使用的, 過多的暴露沒用的方法,只會增加程式碼的維護成本和降低程式碼的可讀性 。(PS:java.util.Collections 類便是這麼操作的)
使用者通過靜態工廠方法獲取所需的例項,而不知具體實現的另一好處是,在API版本升級,或是我們開源專案升級中,替換背後真正的實現類的成本近乎為0。以圖中為例,API為1.0時,Factory 返回的為 C(小樹苗)類,到了API為2.0時,Factory返回的為 B(小樹)類,到API為2.2時,Factory返回的為A(參天大樹)類。而 這一切的替換對使用者已經寫好的程式碼或是將來要寫的程式碼都是不會產生影響的 。
使用者通過靜態工廠方法獲取所需的例項,還有第三個好處,便是我們可以進行配置。這個場景的最好解釋就是眾所周知的JDBC。 各大資料庫廠商只需實現對應的介面規則,然後注入服務後,使用者便可進行操作。 我們還是繼續以圖來舉例,假設現在來了 D(聖誕樹)類,只需要實現 Tree 介面,然後到 Fractory 進行“登記”(具體我們可以使用Map之類的集合進行持有),最後等到使用者需要時,將其作為返回值返回,但對於使用者來說,D還是一個Tree型別,沒什麼特殊差異的存在。
五、使用 “靜態工廠方法” 有什麼弊端
凡事有利便有弊,認清弊端才能更好的使用這一建議。
(1)類如果不含共有的或者受保護的構造器,就不能被繼承
不含共有的或者受保護的構造器,使用者是無法進行繼承擴充套件的,例如上面手繪圖中,A、B、C三個類不開放構造器,使用者也就是沒法繼承了。但也帶來一個好處,他鼓勵我們使用複合,而非繼承(複合優於繼承)。
(2)他們和其他的靜態方法實際上沒有任何區別
話說回來,其實 靜態工廠方法 和 靜態方法 其實是對編譯器來說是完全一樣的,都是靜態方法。所以對呼叫 API 的使用者來說,需要有一個慣用名稱的協定,讓他們知道這是一個非普通的靜態方法。這裡羅列幾個,以便大家參考:
-
valueOf
型別轉換方法 -
of
與valueOf
,只是一個縮寫 -
getInstance
返回唯一例項 -
newInstance
返回新的例項
六、寫在最後
文章融入一些個人的見解,可能存在一些認知的誤區或是表述不為清晰的地方,請於評論區留言我們進行討論。如果文章對你的開發思維有些許的啟發,請給我一個贊:heart:或是關注我吧,技術之路路漫漫,讓我們共同成長。
最後如果想更多的討論或是瞭解我的話,可以通過一下二維碼加我微信。
