Builder Pattern(建造者模式)
目的
- 減少構造函數的數量, 去除參數過多的構造函數, 參數過多會引起可讀性和易用性下降
例子代碼
假如我們要建造個女朋友(心疼自己, 別人的女朋友不都是國家給發麼, 我的怎麼還需要自己 new, 國家啥時候給我發到底)
女朋友有很多屬性, 年齡, 性別, (還要有性別, 心疼自己, 帶不帶物種呀, 刪刪刪), 姓名等, 不同階段我們可能知道不同的屬性, 所以我們將女朋友類設計如下:
初始實現
@Getter
@Setter
public class GirlFriend {
/**
* 出生日期
*/
private LocalDateTime birthDay;
/**
* 姓名
*/
private String name;
/**
* 星座
*/
private ConstellationEnum constellation;
/**
* 家鄉
*/
private String hometown;
/**
* 身高
*/
private Integer height;
/**
* 體重
*/
private Integer weight;
/**
* 凶兆
*/
private CupEnum cup;
/**
* 剛認識, 只知道姓名
* @param name
*/
@BadSmell
public GirlFriend(String name) {
this.name = name;
}
/**
* 開始約會聊天, 開始查戶口
* START: 你家哪的呀
*
* G: 內蒙古
* I: 大草原, 騎馬上學, 考試考射箭
* G: 福建
* I: 賣茶的
* G: 北京
* I: 你們那霧霾走路上看不到人吧
* G: 四川
* I: 你們那辣呀
* G: 合肥
* I: ... 合肥有啥
* @param birthDay
* @param name
* @param constellation
* @param hometown
* @param height
* @param weight
*/
@BadSmell
public GirlFriend(LocalDateTime birthDay, String name, ConstellationEnum constellation, String hometown, Integer height, Integer weight) {
this.birthDay = birthDay;
this.name = name;
this.constellation = constellation;
this.hometown = hometown;
this.height = height;
this.weight = weight;
}
/**
* 開始深入聊天 ...
* 不然呢, 你以爲最後一個入參怎麼知道的
* 什麼? 什麼摸, 摸什麼的
* @param birthDay
* @param name
* @param constellation
* @param hometown
* @param height
* @param weight
* @param cup
*/
@BadSmell
public GirlFriend(LocalDateTime birthDay, String name, ConstellationEnum constellation, String hometown, Integer height, Integer weight, CupEnum cup) {
this.birthDay = birthDay;
this.name = name;
this.constellation = constellation;
this.hometown = hometown;
this.height = height;
this.weight = weight;
this.cup = cup;
}
}
問題分析
我們覺得有三個問題
一. 構造函數數量太多啦
如果我們是個老司機, 我們久經沙場, 一眼就能看出女朋友的 cup, 我們就可能再增加一個構造函數如下:
/**
* 老司機構造函數
* @param name
* @param cup
*/
@BadSmell
public GirlFriend(String name, CupEnum cup) {
this.name = name;
this.cup = cup;
}
如果我們是朋友介紹的, 說是個年輕小姑娘, 才18 歲(洗洗睡吧, 18 歲的小姑娘看不上你的), 我們就知道她的年齡, 甚至不知道姓名, 我們就得再增加一個構造函數
/**
* 朋友介紹, 只說了是個 18 歲的姑娘
* @param birthDay
*/
@BadSmell
public GirlFriend(LocalDateTime birthDay) {
this.birthDay = birthDay;
}
二. 第三個的入參太多啦, 身高體重還都是 Integer 類型, 一個填錯就把 168 cm, 50 kg 的女票變成了 50 cm, 168 kg 的女朋友(再次心疼自己, 168 kg 的都沒找到)[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-g64r9N9q-1587476071932)(/assets/2019091403.png)]
三. 對新屬性的擴展性極差
如果我們今天新想到了一個屬性需要記錄下來, 比如女朋友職業(學生, 白領, 文員, 程序媛, 教師), 比如血型等等, 我們每次增加第 n 個屬性屬性, 如果想要全部構造出來, 可能需要添加 2^(n - 1) 構造函數, 這個是很絕望的
初步嘗試解決
看了上面的分析, 我們靈光乍現, 既然想使用構造函數構造個女朋友這麼複雜, 那我們就不構造了在生活中找一個不就好啦
我們既然是個優秀的程序員, 解決這個問題比在生活中找一個女朋友容易多啦(至少構造函數不嫌我們脫髮呀)
好, 那應該怎麼解決呢, 既然構造函數數量以指數級別膨脹, 那我們不用構造函數了行不行, 使用無參構造函數和 Set 方法寫下怎麼樣呢
/**
* 使用無參構造函數和 Setter 完成對象拼裝
*/
public static void buildGirlFriendWithSetter() {
GirlFriend girlFriend = new GirlFriend();
girlFriend.setBirthDay(LocalDateTime.now());
girlFriend.setName("");
girlFriend.setConstellation(null);
girlFriend.setHometown("");
girlFriend.setHeight(0);
girlFriend.setWeight(0);
girlFriend.setCup(null);
...
}
這個缺點很明顯啦, 代碼太長, set 一行又一行
使用 Builder 模式
public final class GirlFriendBuilder {
private LocalDateTime birthDay;
private String name;
private ConstellationEnum constellation;
private String hometown;
private Integer height;
private Integer weight;
private CupEnum cup;
private GirlFriendBuilder() {
}
public static GirlFriendBuilder aGirlFriend() {
return new GirlFriendBuilder();
}
public GirlFriendBuilder withBirthDay(LocalDateTime birthDay) {
this.birthDay = birthDay;
return this;
}
public GirlFriendBuilder withName(String name) {
this.name = name;
return this;
}
public GirlFriendBuilder withConstellation(ConstellationEnum constellation) {
this.constellation = constellation;
return this;
}
public GirlFriendBuilder withHometown(String hometown) {
this.hometown = hometown;
return this;
}
public GirlFriendBuilder withHeight(Integer height) {
this.height = height;
return this;
}
public GirlFriendBuilder withWeight(Integer weight) {
this.weight = weight;
return this;
}
public GirlFriendBuilder withCup(CupEnum cup) {
this.cup = cup;
return this;
}
public GirlFriend build() {
GirlFriend girlFriend = new GirlFriend();
girlFriend.setBirthDay(birthDay);
girlFriend.setName(name);
girlFriend.setConstellation(constellation);
girlFriend.setHometown(hometown);
girlFriend.setHeight(height);
girlFriend.setWeight(weight);
girlFriend.setCup(cup);
return girlFriend;
}
}
使用上面的代碼就可以構建一個自定義的女朋友啦
GirlFriend cuiHuaGirlFriend = GirlFriendBuilder.aGirlFriend().withName("翠花").withHeight(168).withWeight(50).build();
之前所說的一些問題都解決啦
最終的類圖如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2U15JG2d-1587476071942)(/assets/2019091501.png)]
生產實踐
可以使用一些插件來幫助我們完成重複開發的工作, IDEA 插件的安裝請參考 IDEA 安裝插件的方法
使用 Builder 插件
插件地址: https://plugins.jetbrains.com/plugin/6585-builder-generator/
code -> generate[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Co0eDrNy-1587476071943)(/assets/2019091404.png)]
選擇 builder 就可以啦
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-I331GP4Y-1587476071944)(/assets/2019091405.png)]
使用 Lombok 插件
插件地址: https://plugins.jetbrains.com/plugin/6317-lombok/
mavn 引入 lombok 依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
在需要使用的類上面打上標籤
@Builder
public class GirlFriend {
/**
* 出生日期
*/
private LocalDateTime birthDay;
...
}
使用方式:
GirlFriend girlFriend = GirlFriend.builder().birthDay(LocalDateTime.now()).build();
使用 Setter 插件
插件地址: https://plugins.jetbrains.com/plugin/9360-generateallsetter/
微信掃碼有驚喜: