設(shè)計模式之建造者模式(BuilderPattern)
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達(dá)
作者 | 敲代碼的小小酥
來源 | urlify.cn/zmuaey
76套java從入門到精通實(shí)戰(zhàn)課程分享
一.意義
將一個復(fù)雜的對象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
說明:復(fù)雜對象的構(gòu)建,比如一個對象有幾十個成員屬性,那么我們在創(chuàng)建這個對象,并給成員屬性賦值時,就會很麻煩。采用建造者模式,就是把創(chuàng)建對象并給成員屬性賦值的工作,分離出來,由建造者角色來完成,業(yè)務(wù)程序員直接調(diào)用導(dǎo)演類,獲得復(fù)雜對象即可,無需再進(jìn)行對象的創(chuàng)建工作了。
二.角色
建造者模式涉及到一下幾種角色:
Builder(抽象建造者):它為創(chuàng)建一個產(chǎn)品Product對象的各個部件指定抽象接口,在該接口中一般聲明兩類方法,一類方法是buildPartX(),它們用于創(chuàng)建復(fù)雜對象的各個部件;另一類方法是getResult(),它們用于返回復(fù)雜對象。Builder既可以是抽象類,也可以是接口。由此可見,抽象建造者就類似于抽象工廠,只不過,抽象工廠是調(diào)度的產(chǎn)品的整體部件,而抽象建造者,是構(gòu)造產(chǎn)品的內(nèi)部部件。抽象建造者定義了建造規(guī)范,不同的建造者遵循著同樣的建造規(guī)范,構(gòu)建不同的部件。
ConcreteBuilder(具體建造者):它實(shí)現(xiàn)了Builder接口,實(shí)現(xiàn)各個部件的具體構(gòu)造和裝配方法,定義并明確它所創(chuàng)建的復(fù)雜對象,也可以提供一個方法返回創(chuàng)建好的復(fù)雜產(chǎn)品對象。具體建造者就相當(dāng)于工廠實(shí)現(xiàn)類。一個產(chǎn)品,對應(yīng)一個具體建造者,構(gòu)造多樣的部件,并返回對應(yīng)的產(chǎn)品對象。
Product(產(chǎn)品角色):它是被構(gòu)建的復(fù)雜對象,包含多個組成部件,具體建造者創(chuàng)建該產(chǎn)品的內(nèi)部表示并定義它的裝配過程。在建造者模式中,產(chǎn)品大多是一個類,產(chǎn)品的部件就是類的成員屬性。
Director(指揮者):指揮者又稱為導(dǎo)演類,它負(fù)責(zé)安排復(fù)雜對象的建造次序,指揮者與抽象建造者之間存在關(guān)聯(lián)關(guān)系,可以在其construct()建造方法中調(diào)用建造者對象的部件構(gòu)造與裝配方法,完成復(fù)雜對象的建造??蛻舳艘话阒恍枰c指揮者進(jìn)行交互,在客戶端確定具體建造者的類型,并實(shí)例化具體建造者對象(也可以通過配置文件和反射機(jī)制),然后通過指揮者類的構(gòu)造函數(shù)或者Setter方法將該對象傳入指揮者類中。
個人感覺,這里的導(dǎo)演類,完全可以融合到建造者角色中,這樣的話,對于調(diào)用者而言,選擇具體建造者的時候,通過建造者構(gòu)造部件,也可以獲得產(chǎn)品。這種形式,就是把產(chǎn)品的構(gòu)建和產(chǎn)品的獲得,都放到了一個類里,違反了單一責(zé)任原則。所以,導(dǎo)演類的存在,是為了滿足單一責(zé)任原則。那為什么在工廠模式中,沒有導(dǎo)演這個角色呢,那是因?yàn)樵诠S模式中,工廠的作用就是生產(chǎn)產(chǎn)品,直接通過工廠,就獲得了產(chǎn)品。而建造者模式,建造者是在構(gòu)建產(chǎn)品,它體現(xiàn)的是一個過程,而不是最終的結(jié)果。
三、代碼實(shí)現(xiàn)
我們來構(gòu)建上述的角色,實(shí)現(xiàn)建造者模式:
首先,先定義產(chǎn)品,我們以電腦產(chǎn)品為例,建造者模式的產(chǎn)品以類為主,注重的是內(nèi)部的部件(成員屬性):
/**
* 產(chǎn)品:電腦
*/
public class Computer {
/**
* 內(nèi)部部件:
*/
private String brand;
private String cpu;
private String mainBoard;
private String hardDisk;
private String displayCard;
private String power;
private String memory;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getMainBoard() {
return mainBoard;
}
public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}
public String getHardDisk() {
return hardDisk;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
public String getDisplayCard() {
return displayCard;
}
public void setDisplayCard(String displayCard) {
this.displayCard = displayCard;
}
public String getPower() {
return power;
}
public void setPower(String power) {
this.power = power;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
} 然后,我們定義抽象創(chuàng)建者,主要分為兩部分:buildX構(gòu)建部件,和getResult獲取構(gòu)建產(chǎn)品,代碼如下:
**
* 建造者角色,其實(shí)就是代替復(fù)雜對象的構(gòu)造函數(shù)等構(gòu)造方式,在這里對復(fù)雜對象進(jìn)行構(gòu)建
*/
public abstract class ComputerBuilder {
//建造者中,創(chuàng)建復(fù)雜產(chǎn)品對象,構(gòu)建出復(fù)雜對象后,進(jìn)行輸出。
protected Computer computer = new Computer();
/**
* 建造產(chǎn)品部件的方法,這里用抽象方法,不同的建造者可以構(gòu)建出不同的產(chǎn)品部件來。
*/
public abstract void buildBrand();
public abstract void buildCPU();
public abstract void buildMainBoard();
public abstract void buildHardDisk();
public abstract void buildDisplayCard();
public abstract void buildPower();
public abstract void buildMemory();
/**
* 建造者最后輸出復(fù)雜產(chǎn)品。
* @return
*/
public Computer createComputer() {
return computer;
}
}然后創(chuàng)建具體建造者類,如下代碼:
/**
* 具體建造者,可以構(gòu)建不同的產(chǎn)品部件。構(gòu)建者有統(tǒng)一的構(gòu)建接口,這樣可以規(guī)范不同構(gòu)建者。
*/
public class ASUSComputerBuilder extends ComputerBuilder{
@Override
public void buildBrand() {
computer.setBrand("華碩電腦");
}
@Override
public void buildCPU() {
computer.setCpu("Intel 第8代 酷睿");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("華碩主板");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("256GB SSD");
}
@Override
public void buildDisplayCard() {
computer.setDisplayCard("MX150 獨(dú)立2GB");
}
@Override
public void buildPower() {
computer.setPower("3芯 鋰離子電池 65W AC適配器");
}
@Override
public void buildMemory() {
computer.setMemory("1 x SO-DIMM 8GB");
}
}
/**
* 具體建造者,可以構(gòu)建不同的產(chǎn)品部件。構(gòu)建者有統(tǒng)一的構(gòu)建接口,這樣可以規(guī)范不同構(gòu)建者。
*/
public class DellComputerBuilder extends ComputerBuilder {
@Override
public void buildBrand() {
computer.setBrand("戴爾電腦");
}
@Override
public void buildCPU() {
computer.setCpu("i5-8300H 四核");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("戴爾主板");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("1T + 128GB SSD");
}
@Override
public void buildDisplayCard() {
computer.setDisplayCard("GTX1060 獨(dú)立6GB");
}
@Override
public void buildPower() {
computer.setPower("4芯 鋰離子電池 180W AC適配器");
}
@Override
public void buildMemory() {
computer.setMemory("4G + 4G");
}
}然后,我們定義導(dǎo)演類,來調(diào)度建造者,輸出復(fù)雜產(chǎn)品:
/**
* 導(dǎo)演類,指揮構(gòu)建過程,調(diào)用建造者類。
*/
public class ComputerDirector {
/**
* 導(dǎo)演類的建造方法一般都用construct命名。傳入具體建造者類,輸出產(chǎn)品
* @param builder
* @return
*/
public Computer construct(ComputerBuilder builder){
Computer computer;
builder.buildBrand();
builder.buildCPU();
builder.buildDisplayCard();
builder.buildHardDisk();
builder.buildMainBoard();
builder.buildMemory();
builder.buildPower();
computer = builder.createComputer();
return computer;
}
}這樣,對于業(yè)務(wù)程序員而言,就可以通過導(dǎo)演類,來獲取復(fù)雜對象了,如下:
public class Main {
public static void main(String[] args) {
//用戶直接使用的是導(dǎo)演類
ComputerDirector director=new ComputerDirector();
//選擇相應(yīng)的構(gòu)建者,來構(gòu)建不同的部件
ComputerBuilder dellBuilder = new DellComputerBuilder();
//返回構(gòu)建出來的產(chǎn)品
Computer dellComputer=director.construct(dellBuilder);
}
}由此我們可以看到,業(yè)務(wù)程序員需要選擇具體的建造類,傳入導(dǎo)演類中,獲得產(chǎn)品。在我們實(shí)際項(xiàng)目中,通常我們在xml里定義需要的具體建造者類,然后spring等其他框架讀取我們在xml里寫的具體建造者類,調(diào)用導(dǎo)演類,來獲得程序員想要的產(chǎn)品。所以對于業(yè)務(wù)程序員而言,需要做的就是在xml里定義具體建造者,導(dǎo)演類都是框架在做。
四、優(yōu)點(diǎn)和缺點(diǎn)
優(yōu)點(diǎn):
在建造者模式中,客戶端不必知道產(chǎn)品內(nèi)部組成的細(xì)節(jié),將產(chǎn)品本身與產(chǎn)品的創(chuàng)建過程解耦,使得相同的創(chuàng)建過程可以創(chuàng)建不同的產(chǎn)品對象。
每一個具體建造者都相對獨(dú)立,而與其他的具體建造者無關(guān),因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產(chǎn)品對象。由于指揮者類針對抽象建造者編程,增加新的具體建造者無須修改原有類庫的代碼,系統(tǒng)擴(kuò)展方便,符合 “開閉原則”。
可以更加精細(xì)地控制產(chǎn)品的創(chuàng)建過程。將復(fù)雜產(chǎn)品的創(chuàng)建步驟分解在不同的方法中,使得創(chuàng)建過程更加清晰,也更方便使用程序來控制創(chuàng)建過程。
缺點(diǎn):
建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點(diǎn),其組成部分相似,如果產(chǎn)品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用范圍受到一定的限制。
如果產(chǎn)品的內(nèi)部變化復(fù)雜,可能會導(dǎo)致需要定義很多具體建造者類來實(shí)現(xiàn)這種變化,導(dǎo)致系統(tǒng)變得很龐大,增加系統(tǒng)的理解難度和運(yùn)行成本。從這句話我們可以看出,在我們應(yīng)用設(shè)計模式時,也要考慮系統(tǒng)的復(fù)雜程度,即使一個業(yè)務(wù)很適合應(yīng)用某種設(shè)計模式,但是應(yīng)用起來,很笨重很復(fù)雜,那么我們也不建議生搬硬套設(shè)計模式。
五、建造者模式的本質(zhì)
建造者模式的本質(zhì),就是建造對象。我們在閱讀源碼中,看到Builder字樣時,只要知道其實(shí)在創(chuàng)造產(chǎn)品即可。具體其怎么走流程,怎么創(chuàng)建這個對象的,我們不用太過關(guān)注,因?yàn)橛行┙ㄔ煺吣J?,結(jié)構(gòu)和設(shè)計特別復(fù)雜。
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
