概念
-
將一個(gè)復(fù)雜對(duì)象的構(gòu)造與它的表示分離,使同樣的構(gòu)建過程可以創(chuàng)建不同的表示。它使將一個(gè)復(fù)雜的對(duì)象分解成多個(gè)簡(jiǎn)單的對(duì)象,然后一步步構(gòu)建而成。
-
每一個(gè)具體建造者都相對(duì)獨(dú)立,而與其它的具體建造者無關(guān),因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產(chǎn)品對(duì)象。由于指揮者類針對(duì)抽象建造者編程,增加新的具體建造者無須修改原有類庫的代碼,系統(tǒng)擴(kuò)展方便,符合“開閉原則”。
未用建造者模式
-
以下舉個(gè)最簡(jiǎn)單的例子:電腦配件(包括品牌、價(jià)格、描述)、組裝電腦。
電腦接口
/**
* 電腦接口
*/
public interface Computer {
/**
* 組件(主機(jī)Host、顯示器Monitor、鼠標(biāo)Mouse、鍵盤Keyboard)
*/
String parts();
/**
* 品牌
*/
String brand();
/**
* 價(jià)格
*/
Double price();
/**
* 描述
*/
String desc();
}
主機(jī)Host
/**
* 惠普主機(jī)
*/
public class HPHost implements Computer {
@Override
public String parts() {
return "惠普主機(jī)";
}
@Override
public String brand() {
return "惠普品牌";
}
@Override
public Double price() {
return 6999.00;
}
@Override
public String desc() {
return "HP Computer Welcome";
}
}
/**
* 聯(lián)想主機(jī)
*/
public class LenovoHost implements Computer {
@Override
public String parts() {
return "聯(lián)想主機(jī)";
}
@Override
public String brand() {
return "聯(lián)想品牌";
}
@Override
public Double price() {
return 6899.00;
}
@Override
public String desc() {
return "Lenovo Computer Welcome";
}
}
顯示器Monitor
/**
* 小米顯示器
*/
public class RedmiMonitor implements Computer {
@Override
public String parts() {
return "小米顯示器";
}
@Override
public String brand() {
return "小米品牌";
}
@Override
public Double price() {
return 1399.00;
}
@Override
public String desc() {
return "Redmi Monitor Welcome";
}
}
/**
* 華碩顯示器
*/
public class ROGMonitor implements Computer {
@Override
public String parts() {
return "華碩顯示器";
}
@Override
public String brand() {
return "華碩品牌";
}
@Override
public Double price() {
return 1899.00;
}
@Override
public String desc() {
return "ROG Monitor Welcome";
}
}
鼠標(biāo)Monse
/**
* 羅技鼠標(biāo)
*/
public class GMouse implements Computer {
@Override
public String parts() {
return "羅技鼠標(biāo)";
}
@Override
public String brand() {
return "羅技品牌";
}
@Override
public Double price() {
return 139.00;
}
@Override
public String desc() {
return "G Mouse Welcome";
}
}
/**
* 聯(lián)想鼠標(biāo)
*/
public class LenovoMouse implements Computer {
@Override
public String parts() {
return "聯(lián)想鼠標(biāo)";
}
@Override
public String brand() {
return "聯(lián)想品牌";
}
@Override
public Double price() {
return 89.00;
}
@Override
public String desc() {
return "Lenovo Mouse Welcome";
}
}
鍵盤Keyboard
/**
* 羅技鍵盤
*/
public class GKeyboard implements Computer {
@Override
public String parts() {
return "羅技鍵盤";
}
@Override
public String brand() {
return "羅技品牌";
}
@Override
public Double price() {
return 239.00;
}
@Override
public String desc() {
return "G Keyboard Welcome";
}
}
/**
* 惠普鍵盤
*/
public class HPKeyboard implements Computer {
@Override
public String parts() {
return "惠普鍵盤";
}
@Override
public String brand() {
return "惠普品牌";
}
@Override
public Double price() {
return 89.00;
}
@Override
public String desc() {
return "HP Keyboard Welcome";
}
}
組裝電腦
**
* 組裝電腦
* 不同的套裝配不同的設(shè)備
*/
public class PackageComputer {
/**
* 根據(jù)套餐數(shù)字對(duì)應(yīng)返回整套電腦配置詳情
*
* @param choose 套餐數(shù)字
* @return 電腦配置
*/
public String getComputer(Integer choose) {
// 價(jià)格初始值
double price;
// 組裝電腦配件
List<Computer> parts = new ArrayList<>();
StringBuilder stringBuilder = new StringBuilder();
if(choose == 1) {
HPHost hpHost = new HPHost();
RedmiMonitor redmiMonitor = new RedmiMonitor();
LenovoMouse lenovoMouse = new LenovoMouse();
HPKeyboard hpKeyboard = new HPKeyboard();
// 組裝電腦
parts.add(hpHost);
parts.add(redmiMonitor);
parts.add(lenovoMouse);
parts.add(hpKeyboard);
// 計(jì)算價(jià)格
price = hpHost.price() + redmiMonitor.price() + lenovoMouse.price() + hpKeyboard.price();
stringBuilder.append("套餐為:" + choose + "號(hào)套餐\r\n");
stringBuilder.append("配件如下:\r\n");
for(Computer c : parts) {
stringBuilder.append(c.parts() + "、");
stringBuilder.append(c.brand() + "、");
stringBuilder.append(c.price() + "、");
stringBuilder.append(c.desc() + "\r\n");
}
stringBuilder.append("總價(jià)格為:" + price + "RMB\r\n");
} else if(choose == 2) {
LenovoHost lenovoHost = new LenovoHost();
ROGMonitor rogMonitor = new ROGMonitor();
GMouse gMouse = new GMouse();
GKeyboard gKeyboard = new GKeyboard();
// 組裝電腦
parts.add(lenovoHost);
parts.add(rogMonitor);
parts.add(gMouse);
parts.add(gKeyboard);
// 計(jì)算價(jià)格
price = lenovoHost.price() + rogMonitor.price() + gMouse.price() + gKeyboard.price();
stringBuilder.append("套餐為:" + choose + "號(hào)套餐\r\n");
stringBuilder.append("配件如下:\r\n");
for(Computer c : parts) {
stringBuilder.append(c.parts() + "、");
stringBuilder.append(c.brand() + "、");
stringBuilder.append(c.price() + "、");
stringBuilder.append(c.desc() + "\r\n");
}
stringBuilder.append("總價(jià)格為:" + price + "RMB\r\n");
} else if(choose == 3) {
LenovoHost lenovoHost = new LenovoHost();
RedmiMonitor redmiMonitor = new RedmiMonitor();
GMouse gMouse = new GMouse();
LenovoMouse lenovoMouse = new LenovoMouse();
// 組裝電腦
parts.add(lenovoHost);
parts.add(redmiMonitor);
parts.add(gMouse);
parts.add(lenovoMouse);
// 計(jì)算價(jià)格
price = lenovoHost.price() + redmiMonitor.price() + gMouse.price() + lenovoMouse.price();
stringBuilder.append("套餐為:" + choose + "號(hào)套餐\r\n");
stringBuilder.append("配件如下:\r\n");
for(Computer c : parts) {
stringBuilder.append(c.parts() + "、");
stringBuilder.append(c.brand() + "、");
stringBuilder.append(c.price() + "、");
stringBuilder.append(c.desc() + "\r\n");
}
stringBuilder.append("總價(jià)格為:" + price + "RMB\r\n");
}
return stringBuilder.toString();
}
}
測(cè)試
public class BuilderDesign {
public static void main(String[] args) {
PackageComputer computer = new PackageComputer();
System.out.println(computer.getComputer(1));
System.out.println("=======================================================");
System.out.println(computer.getComputer(2));
System.out.println("=======================================================");
System.out.println(computer.getComputer(3));
}
}
使用建造者模式
-
從上面可以看出來,電腦的每個(gè)配件都要去建對(duì)應(yīng)的類。例子中我給了主機(jī)、顯示器、鼠標(biāo)、鍵盤四種部件,每個(gè)部件假設(shè)兩種品牌,就寫了 2 * 4 = 8個(gè)類。雖說不會(huì)是指數(shù)型增長(zhǎng),但是無論哪個(gè)增加都會(huì)是很明顯的增長(zhǎng)趨勢(shì)。而且在組裝電腦時(shí),要根據(jù)每個(gè)不同要求的去返回對(duì)應(yīng)的信息,每一個(gè)if語句都有二十行代碼左右,看起來十分臃腫。
-
接下來將會(huì)用到建造者模式去優(yōu)化上面的代碼量。
組裝電腦接口
public interface IComputer {
/**
* 主機(jī)
*/
IComputer appendHost(Computer computer);
/**
* 顯示器
*/
IComputer appendMonitor(Computer computer);
/**
* 鼠標(biāo)
*/
IComputer appendMouse(Computer computer);
/**
* 鍵盤
*/
IComputer appendKeyboard(Computer computer);
/**
* @return 電腦清單
*/
String computerDetail();
}
建造者組裝電腦
/**
* 建造者組裝電腦
*/
public class BuilderComputer implements IComputer{
List<Computer> parts = new ArrayList<>();
private double price = 0.00;
private Integer choose;
public BuilderComputer(){}
public BuilderComputer(Integer choose) {
this.choose = choose;
}
@Override
public IComputer appendHost(Computer computer) {
parts.add(computer);
price = price + computer.price();
return this;
}
@Override
public IComputer appendMonitor(Computer computer) {
parts.add(computer);
price = price + computer.price();
return this;
}
@Override
public IComputer appendMouse(Computer computer) {
parts.add(computer);
price = price + computer.price();
return this;
}
@Override
public IComputer appendKeyboard(Computer computer) {
parts.add(computer);
price = price + computer.price();
return this;
}
@Override
public String computerDetail() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("套餐為:" + choose + "號(hào)套餐\r\n");
stringBuilder.append("配件如下:\r\n");
for(Computer c : parts) {
stringBuilder.append(c.parts() + "、");
stringBuilder.append(c.brand() + "、");
stringBuilder.append(c.price() + "、");
stringBuilder.append(c.desc() + "\r\n");
}
stringBuilder.append("總價(jià)格為:" + price + "RMB\r\n");
return stringBuilder.toString();
}
}
建造者
????????去掉了繁瑣的if else,符合單一職責(zé)原則、開閉原則,代碼可讀性、復(fù)用性、拓展性強(qiáng)。這里面就完美的展示了什么叫做將一個(gè)復(fù)雜對(duì)象的構(gòu)造與它的表示分離。并且鏈?zhǔn)骄幊痰恼Z法比不斷的set()要美觀得多,這會(huì)在后續(xù)Lambok中的@Builder中進(jìn)行說明。
/**
* 建造者
*/
public class Builder {
/**
* @return 一號(hào)套餐
*/
public IComputer chooseOne() {
return new BuilderComputer(1)
.appendHost(new HPHost())
.appendMonitor(new RedmiMonitor())
.appendMouse(new LenovoMouse())
.appendKeyboard(new HPKeyboard());
}
/**
* @return 二號(hào)套餐
*/
public IComputer chooseTwo() {
return new BuilderComputer(2)
.appendHost(new LenovoHost())
.appendMonitor(new ROGMonitor())
.appendMouse(new GMouse())
.appendKeyboard(new GKeyboard());
}
/**
* @return 三號(hào)套餐
*/
public IComputer chooseThree() {
return new BuilderComputer(3)
.appendHost(new LenovoHost())
.appendMonitor(new RedmiMonitor())
.appendMouse(new GMouse())
.appendKeyboard(new LenovoMouse());
}
}
測(cè)試
public class BuilderDesign {
public static void main(String[] args) {
Builder builder = new Builder();
System.out.println(builder.chooseOne().computerDetail());
System.out.println("=======================================================");
System.out.println(builder.chooseTwo().computerDetail());
System.out.println("=======================================================");
System.out.println(builder.chooseThree().computerDetail());
}
}
@Builder
??????? 此注解是Lombok依賴下的,而Lombok基本是各個(gè)公司都會(huì)使用到的工具包。可以用來簡(jiǎn)化開發(fā)。上面的建造者組裝電腦的示例代碼就是鏈?zhǔn)骄幊痰年P(guān)鍵之處:每個(gè)方法除了會(huì)傳參還會(huì)返回this自身。我創(chuàng)建了一個(gè)用戶User類,其帶有六個(gè)屬性。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String sex;
private Integer age;
private String address;
private String qq;
private String email;
}
底層
??????? 為了驗(yàn)證此注解背后的樣子,最簡(jiǎn)單的實(shí)踐方法就是加上此注解然后查看編譯后的class文件中的代碼。等編譯后我發(fā)現(xiàn)多了以下內(nèi)容。會(huì)發(fā)現(xiàn)多了一個(gè)靜態(tài)內(nèi)部類UserBuilder以及返回User.UserBuilder的build()方法。
??????? 其實(shí)User中的builder()方法以及User類的靜態(tài)內(nèi)部類UserBuilder的build()方法。這兩個(gè)方法名在@Builder注解中已經(jīng)是默認(rèn)的值了。并且或者注解可以用于類、普通方法和構(gòu)造方法上。關(guān)于其底層是如何在User類中生成靜態(tài)內(nèi)部類并且具體的方法代碼塊就不深究Lombok中的源碼了。這里我需要強(qiáng)調(diào)的是使用建造者賦值的時(shí)候就是賦值給其內(nèi)部類屬性的。
優(yōu)勢(shì)
可讀性好
??????? 其實(shí)當(dāng)使用過@Builder這個(gè)注解的時(shí)候就已經(jīng)可以感受到它的好處之一了:美觀且可讀性高。這里我使用了三種創(chuàng)建對(duì)象的方式來作比較出優(yōu)劣處。
??????? 第一個(gè)User對(duì)象使用有參構(gòu)造的真是長(zhǎng)的讓人反胃,甚至如果在真實(shí)的復(fù)雜業(yè)務(wù)場(chǎng)景中,還不知道其中一個(gè)參數(shù)是什么含義,還需要點(diǎn)進(jìn)去看注釋。并且自己使用這種有參構(gòu)造的話,如果沒有背下來每個(gè)位置要放什么參數(shù)那就更麻煩了。所以說有參構(gòu)造的劣勢(shì)就是:可讀性差、參數(shù)過多可能導(dǎo)致傳遞錯(cuò)誤。
????????第二個(gè)User對(duì)象就是一直Setter。相比于第三種而言沒有那么好的可讀性。所以說使用建造者模式的鏈?zhǔn)骄幊炭勺x性好。但是要記住建造者模式的賦值是給其內(nèi)部類屬性的。
public class BuilderDesign {
public static void main(String[] args) {
User u1 = new User("張三x", "男", 18, "福建省廈門市xxx鎮(zhèn)xxxx小區(qū)x樓xxx號(hào)", "465795464", "465795464@qq.com");
User u2 = new User();
u2.setUsername("李四");
u2.setSex("女");
u2.setAge(20);
u2.setAddress("福建省泉州市xxx鎮(zhèn)xxxx小區(qū)x樓xxx號(hào)");
u2.setQq("504899214");
u2.setEmail("504899214@qq.com");
User u3 = User.builder()
.username("王五")
.sex("男")
.age(22)
.address("福建省福州市xxx鎮(zhèn)xxxx小區(qū)x樓xxx號(hào)")
.qq("684354768")
.email("684354768@qq.com")
.build();
}
}
JavaBean創(chuàng)建
??????? 我曾在某個(gè)地方看到一個(gè)大佬說過使用set()方法注入屬性和靜態(tài)內(nèi)部類Builder注入屬性值的區(qū)別,但具體怎么說的已經(jīng)忘記了,
??????? 這里由衷希望看到這里的讀者可以在評(píng)論里說一下關(guān)于JavaBean賦值可能涉及到的線程安全問題或者其它問題。謝謝。
避坑
??????? 在上面有說過一個(gè)問題就是:使用builder()方法賦值是賦值給其靜態(tài)內(nèi)部類建造者類的。那么這句話是什么意思呢?這句話的意思就是當(dāng)我們?cè)趯?shí)體類上已經(jīng)附帶初始值了,但是使用建造者模式去構(gòu)建實(shí)體類打印toString()方法出來的時(shí)候是看到為類加載的初始值的(比如0/false/null等)。具體看以下代碼以及控制臺(tái)輸出。
public class BuilderDesign {
public static void main(String[] args) {
User u = User.builder()
.username("王五")
.sex("男")
.address("福建省福州市xxx鎮(zhèn)xxxx小區(qū)x樓xxx號(hào)")
.qq("684354768")
.email("684354768@qq.com")
.build();
System.out.println(u);
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class User {
private String username;
private String sex;
private Integer age = 30;
private String address;
private String qq;
private String email;
}
????????可以看到age = null。因?yàn)閍ge是包裝類型Integer,所以類加載時(shí)的初始值為null,而不是0。這里的原因就是User的age屬性初始值為30,但是其內(nèi)部的UserBuilder類的age屬性并沒有,所以導(dǎo)致獲取到的User對(duì)象的age屬性為初始值null。為了避免這個(gè)情況發(fā)生,@Builder注解中有一個(gè)內(nèi)部注解來解決這個(gè)問題,就是@Builder.Default。只需要在設(shè)置初始值的屬性上使用此注解即可。編譯生成的User對(duì)象會(huì)多生成個(gè)靜態(tài)的$default$age()方法。文章來源:http://www.zghlxwxcb.cn/news/detail-634442.html
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String sex;
@Builder.Default
private Integer age = 30;
private String address;
private String qq;
private String email;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-634442.html
到了這里,關(guān)于【Java設(shè)計(jì)模式】建造者模式 & 注解@Builder的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!