一.意義
將一個複雜的物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
說明:複雜物件的構建,比如一個物件有幾十個成員屬性,那麼我們在建立這個物件,並給成員屬性賦值時,就會很麻煩。採用建造者模式,就是把建立物件並給成員屬性賦值的工作,分離出來,由建造者角色來完成,業務程式設計師直接呼叫導演類,獲得複雜物件即可,無需再進行物件的建立工作了。
二.角色
建造者模式涉及到一下幾種角色:
Builder(抽象建造者):它為建立一個產品Product物件的各個部件指定抽象介面,在該介面中一般宣告兩類方法,一類方法是buildPartX(),它們用於建立複雜物件的各個部件;另一類方法是getResult(),它們用於返回複雜物件。Builder既可以是抽象類,也可以是介面。由此可見,抽象建造者就類似於抽象工廠,只不過,抽象工廠是排程的產品的整體部件,而抽象建造者,是構造產品的內部部件。抽象建造者定義了建造規範,不同的建造者遵循著同樣的建造規範,構建不同的部件。
ConcreteBuilder(具體建造者):它實現了Builder介面,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜物件,也可以提供一個方法返回建立好的複雜產品物件。具體建造者就相當於工廠實現類。一個產品,對應一個具體建造者,構造多樣的部件,並返回對應的產品物件。
Product(產品角色):它是被構建的複雜物件,包含多個組成部件,具體建造者建立該產品的內部表示並定義它的裝配過程。在建造者模式中,產品大多是一個類,產品的部件就是類的成員屬性。
Director(指揮者):指揮者又稱為導演類,它負責安排複雜物件的建造次序,指揮者與抽象建造者之間存在關聯關係,可以在其construct()建造方法中呼叫建造者物件的部件構造與裝配方法,完成複雜物件的建造。客戶端一般只需要與指揮者進行互動,在客戶端確定具體建造者的型別,並例項化具體建造者物件(也可以通過配置檔案和反射機制),然後通過指揮者類的建構函式或者Setter方法將該物件傳入指揮者類中。
個人感覺,這裡的導演類,完全可以融合到建造者角色中,這樣的話,對於呼叫者而言,選擇具體建造者的時候,通過建造者構造部件,也可以獲得產品。這種形式,就是把產品的構建和產品的獲得,都放到了一個類裡,違反了單一責任原則。所以,導演類的存在,是為了滿足單一責任原則。那為什麼在工廠模式中,沒有導演這個角色呢,那是因為在工廠模式中,工廠的作用就是生產產品,直接通過工廠,就獲得了產品。而建造者模式,建造者是在構建產品,它體現的是一個過程,而不是最終的結果。
三、程式碼實現
我們來構建上述的角色,實現建造者模式:
首先,先定義產品,我們以電腦產品為例,建造者模式的產品以類為主,注重的是內部的部件(成員屬性):
/** * 產品:電腦 */ public class Computer { /** * 內部部件: */ 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; } }
然後,我們定義抽象建立者,主要分為兩部分:buildX構建部件,和getResult獲取構建產品,程式碼如下:
** * 建造者角色,其實就是代替複雜物件的建構函式等構造方式,在這裡對複雜物件進行構建 */ public abstract class ComputerBuilder { //建造者中,建立複雜產品物件,構建出複雜物件後,進行輸出。 protected Computer computer = new Computer(); /** * 建造產品部件的方法,這裡用抽象方法,不同的建造者可以構建出不同的產品部件來。 */ 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(); /** * 建造者最後輸出複雜產品。 * @return */ public Computer createComputer() { return computer; } }
然後建立具體建造者類,如下程式碼:
/** * 具體建造者,可以構建不同的產品部件。構建者有統一的構建介面,這樣可以規範不同構建者。 */ 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 獨立2GB"); } @Override public void buildPower() { computer.setPower("3芯 鋰離子電池 65W AC介面卡"); } @Override public void buildMemory() { computer.setMemory("1 x SO-DIMM 8GB"); } }
/** * 具體建造者,可以構建不同的產品部件。構建者有統一的構建介面,這樣可以規範不同構建者。 */ 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 獨立6GB"); } @Override public void buildPower() { computer.setPower("4芯 鋰離子電池 180W AC介面卡"); } @Override public void buildMemory() { computer.setMemory("4G + 4G"); } }
然後,我們定義導演類,來排程建造者,輸出複雜產品:
/** * 導演類,指揮構建過程,呼叫建造者類。 */ public class ComputerDirector { /** * 導演類的建造方法一般都用construct命名。傳入具體建造者類,輸出產品 * @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; } }
這樣,對於業務程式設計師而言,就可以通過導演類,來獲取複雜物件了,如下:
public class Main { public static void main(String[] args) { //使用者直接使用的是導演類 ComputerDirector director=new ComputerDirector(); //選擇相應的構建者,來構建不同的部件 ComputerBuilder dellBuilder = new DellComputerBuilder(); //返回構建出來的產品 Computer dellComputer=director.construct(dellBuilder); } }
由此我們可以看到,業務程式設計師需要選擇具體的建造類,傳入導演類中,獲得產品。在我們實際專案中,通常我們在xml裡定義需要的具體建造者類,然後spring等其他框架讀取我們在xml裡寫的具體建造者類,呼叫導演類,來獲得程式設計師想要的產品。所以對於業務程式設計師而言,需要做的就是在xml裡定義具體建造者,導演類都是框架在做。
四、優點和缺點
優點:
-
在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的建立過程解耦,使得相同的建立過程可以建立不同的產品物件。
-
每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,使用者使用不同的具體建造者即可得到不同的產品物件。由於指揮者類針對抽象建造者程式設計,增加新的具體建造者無須修改原有類庫的程式碼,系統擴充套件方便,符合 “開閉原則”。
-
可以更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不同的方法中,使得建立過程更加清晰,也更方便使用程式來控制建立過程。
缺點:
-
建造者模式所建立的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用範圍受到一定的限制。
-
如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和執行成本。從這句話我們可以看出,在我們應用設計模式時,也要考慮系統的複雜程度,即使一個業務很適合應用某種設計模式,但是應用起來,很笨重很複雜,那麼我們也不建議生搬硬套設計模式。
五、建造者模式的本質
建造者模式的本質,就是建造物件。我們在閱讀原始碼中,看到Builder字樣時,只要知道其實在創造產品即可。具體其怎麼走流程,怎麼建立這個物件的,我們不用太過關注,因為有些建造者模式,結構和設計特別複雜。