最近棧長在做 Code Review 時,發現一段建立對象的方法:
真實代碼敏感性,上面的代碼僅為模仿,實際要比這個更長、更複雜……
當我看到那段代碼時,我簡直要瘋了!!
拖了半天才看完,到處充滿着魔法值不說,把一個類所有參數都放在一個構造器裡面,這個構造器也太瘋狂了……這種寫法也實在太 low 了!
在實際開發過程中,棧長經常看到同僚們這樣的寫法,比上面的更長的構造器你見過沒?我反正見過!
一方面,也許他們真不知道怎麼寫才更好,畢竟經驗有限,這個可以原諒。但另一方面,也許他們就是為了偷懶,或者為了趕時間,反正這都是對自己和同僚不負責的表現。
如果你在公司看到同僚寫這樣的優秀代碼,請把這篇文章發給他。
看看大量參數構造器的缺點:
參數過多時,代碼太長,極不優雅,維護極難;
不能判斷出哪些是必須參數,哪些是可選參數,可選參數也得給個預設值;
分不清變量值對應哪個變量,如順序對應錯,很容易造成錯誤;
構造器參數增減時,會影響所有建立該對象的地方,影響擴充性;
構造器正确的用法是隻給出幾個必選、重要參數的構造器,而不是把所有參數放在一個構造器中。
比如我們看下 JDK 線程池的構造器用法:
線程池就把幾個重要的參數封裝成了幾個構造器,這樣使用者就可以根據實際需要調用具體的某個構造器。
再回到同僚寫的那個代碼,寫出那樣長的構造器,我真的服了。
最基本也得寫成這樣吧:
這個建立對象的方式是最普通不過了,也是用的最多的了。
這種寫法雖然看起來很直覺,但是有以下幾個缺點:
參數多的情況下 setter 非常多,代碼非常長,不是很優雅;
不能判斷出哪些是必須參數,哪些是可選參數;
容易漏掉一些參數,并且很難檢查出來;
容易搞錯對象名,造成潛在錯誤,很難排查(如:同時有 user 和 user2,在 user 指派過程中錯誤的複制了 user2 對象);
下面棧長教大家用 Builder 模式改良下,下面是改良後的代碼:
說下簡單思路:
1)在 Bean 類裡面建立一個靜态内部類:XxxBuilder;
2)把 Bean 類所有參數複制到 XxxBuilder,然後在 XxxBuilder 建立必須參數的構造器,其他參數使用變量名作為方法然後傳回自身(this)以便形成鍊式調用;
3)在 Bean 類裡面建立一個接收 XxxBuilder 參數的私有構造器,避免使用 new 建立對象;
4)在 XxxBuilder 類建立一個 build 方法開始建構 Bean 類,也是作為鍊式調用的結束;
使用方法:
使用方式如下,先建立構造器,然後在每個方法後使用 . 帶出所有方法,一目了然,最後調用 build 方法以結束鍊式調用建立 bean。

參考代碼如下:
結果輸出:
Task{id=99, name='緊急任務', content='處理一下這個任務', type=1, status=0, finishDate=...
Builder 模式的優點:
鍊式調用,優雅、清晰、一目了然;
一行代碼完成對象建立,避免了多行代碼指派過程出錯;
省去了大量備援變量,避免變量複制出錯;
Builder 模式的缺點:
需要備援的 Builder 類,以及大量相等重複的成員變量,大大增加了代碼量,維護難度相對較大;
隻适合一次指派建立對象,多次指派的場景還需要新增 set 方法配合,不是很靈活;
正常的 Builder 模式需要新增大量的代碼,維護難度比較大,這裡棧長再介紹一下 Lombok 中的 Builder 模式,一個 @Builder 注解搞定所有,輕松維護。
用 Lombok 改良後的代碼如下:
我還能說什麼?兩個字:真香!
再來看下怎麼使用:
或者 new 都不要了,直接調用靜态方法:
接下來我們來看下這個 @Builder 注解到底做了什麼:
這是反編譯後的代碼,可以看出來邏輯都是一樣的。
Lombok 還可以添加各種類構造器、toString 等系列注解,幾個注解完全可以達到想要的效果,但代碼量和可維護性是天壤之别。
很多人不建議使用 Lombok,仁者見仁,智者見智,這裡不再讨論,相關話題可以閱讀我之前寫的文章:
推薦一款代碼神器,代碼量至少省一半!
公司來了個新同僚不會用 Lombok,還說我代碼有問題!
使用 Lombok 帶來了很多便利,不用多說,是真的香,東西是好東西,就是要團隊規範一起使用,避免踩坑。更多工具系列使用文章請關注公衆号Java技術棧,在菜單中閱讀。
Java 8 帶來了函數式接口程式設計,是以在 Java 8 中可以一個實作通用的 Builder:
參考:
http://www.ciphermagic.cn/java8-builder.html
使用方式:
這樣一來,任何帶有預設構造器和 set 方法的類都可以使用這個通用的 Builder 模式了。
雖然利用 Java 8 是實作了通用有 Builder 模式,但還是有很多備援的代碼,而且本質還是調用的 set 方法,是以和 set 比起來隻是多了一個鍊式調用而已。
Spring Boot 是現在主流的應用架構,其中也用到了 Builder 模式,可見 Builder 模式的常見性。
下面再來看下 Spring Boot 是怎麼應用 Builder 模式的:
如上代碼所示,這是 Spring Boot 的鍊式啟動方式。
Spring Boot 基礎教程看這裡:
https://github.com/javastacks/spring-boot-best-practice
我們來看它是怎麼做的:
它是新增了一個 XxxBuilder 類:SpringApplicationBuilder,然後在 SpringApplicationBuilder 中新增了個 SpringApplication 的成員變量,然後再新增變量對應的方法。
是以,Spring Boot 隻是用 SpringApplicationBuilder 包裝了一下 SpringApplication 而已,寫法有所不同,但中心思想都是一樣的。這裡就不再示範了,大家也可以了借鑒一下。
本文說了同僚寫的瘋狂的類構造器,然後再介紹了用 set 方法改良,以及使用 4 種 Builder 模式改良的方式,下面來總結一下吧:
正常的 Builder 模式
Lombok 實作 Builder 模式(推薦)
Java 8 實作 Builder 模式
Spring Boot 中的 Builder 模式
如果團隊有使用 Lombok,那麼 Lombok 無疑是最佳推薦的方式,代碼量少,使用簡單,友善維護。其他三種實作方式都各有利弊,大家都可以參考使用。
總之,别再寫瘋狂的類構造器了……
好了,今天的分享就到這裡了,後面棧長我會更新更多 Java 技術實戰及設計模式系列文章,公衆号Java技術棧第一時間推送。
本節教程所有實戰源碼已上傳到這個倉庫:
https://github.com/javastacks/javastack
最後,覺得我的文章對你用收獲的話,動動小手,給個在看、轉發,原創不易,棧長需要你的鼓勵。