1. 程式人生 > >建造者模式 生成器模式 建立型 設計模式(五)

建造者模式 生成器模式 建立型 設計模式(五)

建造者模式 Builder 也叫做生成器模式 在正式開始建造者模式之前,先回顧下抽象工廠模式 本人的所有系列文章都是自己學習的記錄過程,均有比較嚴格的先後順序,如果不清楚抽象工廠模式可以先往前翻翻

從抽象工廠演化

抽象工廠模式是工廠模式的進一步抽象擴充套件 不僅僅可以建立某種等級結構的產品,可以建立一整個產品族的產品 如下圖所示 比如ConcreteCreator1可以建立ConcreteProductA1和ConcreteProductB1 比如ConcreteCreator2可以建立ConcreteProductA2和ConcreteProductB2 一個產品族的兩個產品,是相關聯的或者有共同的約束,比如同一個廠家,執行在同一個平臺下 image_5bebdce6_4d96

示例

考慮這樣一種場景: 你是一個品牌電腦的組裝廠家(組裝代工廠),你擁有電腦的各種零部件(簡單起見,僅僅以主機板和顯示器為例)
各種零部件都由多個廠家生產(簡單起見,假設只有華碩和戴爾) 請你根據使用者需求提供一臺“品牌”電腦 涉及到多個產品等級結構,而且購買品牌電腦,同一品牌就是同一廠家,這是共同限制,有產品族的概念,所以可以使用抽象工廠模式  工廠生產相關聯的產品族下的產品,然後進行組裝 如下圖所示 image_5bebdce6_301a 對於具體的工廠ConcreteCreator1可以建立華碩產品族的產品,主機板和顯示器 對於具體的工廠ConcreteCreator2可以建立戴爾產品族的產品,主機板和顯示器

程式碼

主機板產品等級結構 image_5bebdce6_a12
package builder;
public
interface MainBoard { String desc(); }
package builder;
public class DellMainBoard implements MainBoard {
@Override
public String desc() {
return "DELL mainBoard";
}
}
package builder;
public class AsusMainBoard implements MainBoard {
@Override
public String desc() {
return "ASUS mainBoard";
}
}
顯示器產品等級結構 image_5bebdce7_a15
package builder;
public interface DisplayDevice {
String Desc();
}
package builder;
public class DellDisplayDevice implements DisplayDevice {
@Override
public String Desc() {
return "DELL display device";
}
}
package builder;
public class AsusDisplayDevice implements DisplayDevice {
@Override
public String Desc() {
return "ASUS display device";
}
}
工廠體系結構 image_5bebdce7_68fc
package builder;
public interface Creator {
MainBoard createMainBoard();
DisplayDevice createDisplayDevice();
}
package builder;
public class ConcreateCreatorDell implements Creator {
@Override
public MainBoard createMainBoard() {
return new DellMainBoard();
}
@Override
public DisplayDevice createDisplayDevice() {
return new DellDisplayDevice();
}
}
package builder;
public class ConcreateCreatorAsus implements Creator {
@Override
public MainBoard createMainBoard() {
return new AsusMainBoard();
}

@Override
public DisplayDevice createDisplayDevice() {
return new AsusDisplayDevice();
}
}
電腦類Computer 使用者需要的是一臺電腦,電腦類為Computer Computer包含主機板和顯示器部件  重寫了toString方便檢視資訊,toString中呼叫了主機板和顯示器的desc()方法
package builder;
public class Computer {
private MainBoard mainBoard;
private DisplayDevice displayDevice;
public MainBoard getMainBoard() {
return mainBoard;
}

public void setMainBoard(MainBoard mainBoard) {
this.mainBoard = mainBoard;
}

public DisplayDevice getDisplayDevice() {
return displayDevice;
}

public void setDisplayDevice(DisplayDevice displayDevice) {
this.displayDevice = displayDevice;
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Computer{");
sb.append("mainBoard=").append(mainBoard.desc());
sb.append(", displayDevice=").append(displayDevice.Desc());
sb.append('}');
return sb.toString();
}
}
測試程式碼客戶端 image_5bebdce7_5564 以上,我們就完成了需求 根據使用者的需求,建立指定的產品族的產品,並且將這一系列的產品進行組合生成最終的使用者需要的產品 

抽象工廠的問題

通過,抽象工廠模式進行產品族零部件的生產,然後在客戶端進行加工 完成了我們的需求,但是這其中有明顯的問題 整個的組裝細節與過程,全部暴露在客戶端程式 客戶端程式知道你所有的零部件型別,也知道你所有零部件實現的細節 顧客只是想購買一臺電腦而已,人家為什麼要關心電腦到底有哪些部件,到底如何組裝的? 簡言之,你自己隱私暴露,人家還不稀罕看,嫌煩! 而且,如果需要在多個場景中完成這個組裝生成的過程,怎麼辦?將會出現大量的冗餘程式碼 再者,如果組裝邏輯發生變動,需要維護多個地方,難度非常大 所以一個很自然的想法就是將整個的組裝邏輯進行封裝 為此,我們新增加一個組裝電腦類AssembleComputer 接受一個Creator 作為引數,藉助於Creator進行零部件產品族的建立以及組裝
package builder;
public class AssembleComputer {
Creator creator;
AssembleComputer(Creator creator){
this.creator = creator;
}

public Computer getComputer(){
Computer computer = new Computer();
MainBoard mainBoard = creator.createMainBoard();
DisplayDevice displayDevice = creator.createDisplayDevice();
computer.setMainBoard(mainBoard);
computer.setDisplayDevice(displayDevice);
return computer;
}
}
測試程式碼 image_5bebdce7_4302 經過封裝後,這段程式碼看起來清爽多了,組裝的細節被封裝到了組裝類AssembleComputer之中 客戶端不在需要大量冗餘的程式碼,而且後續擴充套件和維護也比較容易  

封裝下的重構

上面的封裝的組裝邏輯中,我們先把所有的零部件全部都生產出來,然後在一口氣進行組裝 雖然是把產品的組裝細節對客戶端程式隱藏了 但是,產品的表示生產和組裝過程仍舊是耦合在一起的,都耦合在了getComputer()方法中 是否還可以進一步的將Computer的各個組成部分與組裝邏輯分離呢? 我們把工廠等級結構和組裝電腦類重構下
package builder;

public interface CreatorRefactor {
void assembleMainBoard();
void assembleDisplayDevice();
Computer getComputer();
}
package builder;
public class ConcreateCreatorDellRefactor implements CreatorRefactor {
private Computer computer = new Computer();
@Override
public void assembleMainBoard() {
computer.setMainBoard(new DellMainBoard());
}
@Override
public void assembleDisplayDevice() {
computer.setDisplayDevice(new DellDisplayDevice());
}
@Override
public Computer getComputer() {
return computer;
}
}
package builder;
public class ConcreateCreatorAsusRefactor implements CreatorRefactor {
private Computer computer = new Computer();
@Override
public void assembleMainBoard() {
computer.setMainBoard(new AsusMainBoard());
}
@Override
public void assembleDisplayDevice() {
computer.setDisplayDevice(new AsusDisplayDevice());
}
@Override
public Computer getComputer() {
return computer;
}
}
可以看得出來重構之後的程式碼中 對於工廠角色來說,不僅僅是生產零部件,生產已經成為基礎功能,還需要完成這一步驟的組裝工作 並且提供最終返回完整產品的方法 具體的工廠中,建立了一個具體的產品引用,並且實現了抽象工廠的規定的協議-->組裝每個步驟以及最終返回具體產品 組裝電腦類重構
package builder;
public class AssembleComputerRefactor {
CreatorRefactor creatorRefactor;
AssembleComputerRefactor(CreatorRefactor creatorRefactor){
this.creatorRefactor = creatorRefactor;
}

public Computer getComputer(){
creatorRefactor.assembleMainBoard();
creatorRefactor.assembleDisplayDevice();
return creatorRefactor.getComputer();
}
}
重構後的程式碼,組裝電腦類AssembleComputerRefactor不在涉及到具體零部件的生產了 生產和每一個零件步驟的組裝已經移交到抽象工廠角色中了 組裝電腦類僅僅涉及的就是組裝流程!流程!流程! 完全不關注具體的到底是什麼東西 測試程式碼 image_5bebdce7_3714 image_5bebdce7_43d6

小結

最終重構後的程式碼形式中: 生產產品的構造工廠CreatorRefactor,規定了負責構建一個完整產品的所有的步驟,並且返回最終產品 實際負責生產的具體工廠角色,實現規定的每個步驟,並且返回最終的具體的產品 AssembleComputerRefactor僅僅包含產品的構建邏輯,也就是加工步驟 將生產產品的所有的步驟,合理的組織在一起 也即是說,它所有的流程的每一個步驟的細節,都是工廠提供的,組裝器這個流水線只是負責步驟的梳理安排 比如,穿衣服的所有步驟有:戴帽子,穿鞋子,穿襪子,穿褲子,穿內褲.... 那麼,這個組裝器 就是將這些步驟合理組織安排 不能先穿鞋子在穿襪子的對吧,應該是 穿內褲,穿褲子,穿襪子,穿鞋子,戴帽子.... 你看,步驟都是一樣的,但是順序可能是有要求的,最終返回結果 其實這就是建造者模式 將複雜產品的構建過程和具體的產品進行分離 管你到底是穿什麼鞋子呢,反正你有穿鞋子這一步驟 管你到底穿哪條褲子,反正你得穿褲子,而且穿衣服的場景下得是先穿褲子才能穿鞋子

意圖

將複雜物件的構建與他的表示進行分離,使得同樣的構建過程可以建立不同的表示 物件的構建與表示進行分離,就是類似組裝過程與內部的零件的分離,一臺電腦的內部表示是各種零件,構建就是組裝的過程 

結構

我們將前面的示例,轉換為標準的建造者模式的稱呼 image_5bebdce7_2e58 角色含義 抽象建造者角色Builder
給出一個抽象介面,以規範產品物件各個組成部分之間的構造
這個抽象的介面給出來構造一個產品的所有步驟以及最終產品的獲取協議(就是其中定義的方法)(上面示例中的CreatorRefactor)
具體的建造者ConcreteBuilder 建立具體的產品的工廠、建造者 1.需要實現Builder中規定的產品建立的所有步驟 2.建造完成後,提供產品例項物件 (上面示例中的ConcreateCreatorDellRefactor 和 ConcreateCreatorAsusRefactor) 指揮者、導演Director
指揮產品的整個建造過程,不涉及具體產品的細節,只關注抽象建造者角色Builder定義的各個步驟的組織安排
具體的ConcreteBuilder 才會關注生產的細節(上面示例中的AssembleComputerRefactor) 產品 Product 最終建立起來的一個複雜的產品物件例項(上面示例中的Computer)

程式碼示例

image_5bebdce7_574c
package buildPattern;
public interface Builder {
void buildPart1();
void buildPart2();
Product buildProduct();
}
package buildPattern;
 
public class ConcreateBuilder implements Builder {
private Product product = new Product();
 
@Override
public void buildPart1() {
//...
}
 
@Override
public void buildPart2() {
//...
}
 
@Override
public Product buildProduct() {
return product;
}
}
package buildPattern;
public class Product {
}
package buildPattern;
 
public class Director {
 
private Builder builder;
 
Director(Builder builder) {
this.builder = builder;
}
 
public Product getProduct() {
builder.buildPart1();
builder.buildPart2();
return builder.buildProduct();
}
}

注意事項

1. 上面示例中只有一個ConcreteBuilder,實際上當然可以有多個,他們都繼承自抽象角色Builder    2. 示例中Product為一個具體的類,當然可以變為抽象角色,這樣所有的產品都是屬於Product的 3. Builder角色中,我們以組裝電腦為例子,看起來好像必須是同一類產品,其實不是必然的 Builder與具體的業務邏輯沒關係,你可以把它簡單的理解為步驟 比如它定義了五個步驟buildPart1(); buildPart2();  ........   buildPart5(); 那麼,比如汽車可能由五個生產步驟組成,比如房子可以有五個步驟建造  Builder約定的只是流程,只是步驟,具體的細節由具體的實現工廠ConcreteBuilder決定了 到底是五個蓋房子的步驟,還是五個造車子的步驟,具體的ConcreteBuilder說了算 4. 如果是非常抽象的幾個步驟,完全都不是一個型別的東西,那麼這個抽象的產品怎麼辦?他們都沒有任何的共性 你可以將返回結果產品的步驟,也就是最終的步驟,從抽象角色Builder中拿出來,每個ConcreteBuilder自己返回自己的產品 或者 你可以提供一個標記介面,標記介面,標記介面,什麼都不做,你就說 房子,車子,都是一種Product~~這樣也可以解決

與抽象工廠對比

最開始我們以抽象工廠模式引申出建造者模式 在建造者模式中重要角色為Director和Builder,其實你會發現,其中的Builder與抽象工廠的Creator是有相似點的  Director只不過是把ConcreteCreator中生產的產品族 進行組裝 但是建造者模式中的Builder,重點不在於生產的零部件是什麼,而是在於步驟的劃分 當然每個步驟可能也是需要“生產”的 可以認為Builder是抽象工廠模式中的Creator的一個變種 Director是抽象工廠模式生產產品後的進一步加工 他的重點在於步驟的組織安排 抽象工廠模式僅僅關注到我生產出來了這一個產品族的各個產品 建造者模式則進一步關注這些東西怎麼構成、組裝成為一個更加複雜的產品的步驟 如果以生產汽車為例 抽象工廠模式在於產生某一產品族的零部件,比如 輪胎 發動機 底盤 建造者模式在於安排建造的過程,安裝底盤 安裝輪胎 安裝發動機 建造者模式的組裝的每個步驟中,可能需要先生產在組裝,也可能只是多個加工步驟 與抽象工廠模式的對比是為了加深理解,如果反倒容易混淆,可以無視

使用場景

對於每種模式的使用場景,只需要理解透徹每種模式的意圖即可 建造者模式的意圖在於複雜物件的內部表示與建立過程進行分離前提就是面對複雜物件的建立  比如 有很多品牌的膝上型電腦,電腦包括很多零部件 cpu 顯示卡 記憶體條 顯示器等等  有很多品牌的汽車,汽車包括很多零部件 底盤 發動機 輪胎 輪轂 等等 遊戲中有很多個人物角色 他們都有 性別 髮型 膚色 衣服 面板 等等 如何構造這些複雜的物件 而且還能夠容易新增加新品牌的膝上型電腦和汽車,增加新的人物角色,也就是擴充套件性好 你就可以考慮建造者模式 建造者模式中的Director作為指揮者、導演,僅僅關心步驟的順序安排 不管什麼品牌的膝上型電腦,步驟都是一樣的,安裝cpu 安裝顯示卡 安裝記憶體條...等等 不管是什麼品牌的汽車,生產步驟是一樣的,安裝底盤,安裝發動機...等等 不管什麼樣子的人物角色,建立步驟是一樣的,設定性別,設定膚色...等等 具體的建造者ConcreteBuilder才會關心每個步驟到底做的是什麼事情 將步驟與具體表示分離,當需要擴充套件時,Director部分完全不需要變動,只需要增加新的ConcreteBuilder 即可 通過新的ConcreteBuilder , Director就可以創建出來新的產品、人物角色 建造者模式的關鍵就在於,複雜的物件,構建過程與內部表示的分離 所以當有複雜的內部結構時,或者步驟之間具有嚴格的順序關係就可以考慮建造者模式 步驟不一樣是否可用? 上面反覆強調,他們擁有相同的步驟 那麼,如果一個產品擁有三個步驟,另外一個產品擁有五個步驟 是否還能夠使用建造者模式呢? 當然也是可以的 在抽象的Builder角色中,你仍然需要設定五個步驟 但是對於生產只需要三個步驟的產品的那個ConcreteBuilder 你可以將 buildPart4();buildPart5(); 實現為空方法即可 比如  void buildPart4(){ //什麼都不做。。。。 } 所以,假如說,你定義了一個抽象角色Builder,他有N個步驟,那麼他就可以構造1~N個步驟下,可以實現的所有產品!!! 細節由具體的ConcreteBuilder決定就好了,當然,一般你並不會那麼做 

簡化形式

設計模式都不是一成不變的,可以根據實際情況進行調整甚至變種 如果確定系統中,只需要一個具體的建造者的話,那麼就可以省略抽象的Builder角色 抽象的Builder就是為了規範多個具體Builder建造者的行為,如果只有一個具體的建造者,則失去了意義 此時,這個具體的建造者,也充當了抽象的Builder的角色 image_5bebdce7_49b4 如果已經省略了抽象的Builder 那麼還可以繼續省略Director角色 ConcreteBuilder,也充當了這個Director角色 ConcreteBuilder自己不僅僅實現所有步驟的細節,並且還負責組裝 說白了就是Director中的方法邏輯移植到ConcreteBuilder中, 客戶端從ConcreteBuilder中獲取產品  

建造者與構造方法

假設有一個MyObject類,他有很多屬性,假定目前有v1~v7 ,總共7個 其中v1 和 v2 是必選,其餘為可選屬性 對於這種情況,我們經常使用層疊的構造方法 層層巢狀呼叫 但是這種方式不夠清晰,比較容易犯錯,而且,很多時候即使引數寫顛倒了,也並不會一定導致編譯器報錯 另外一種方式就是藉助於建造者模式的簡化形式 如下面示例
package simplebuilder;
 
/**
* Created by noteless on 2018/10/17.
* Description:假定有一個MyObject類,有7個屬性前面兩個v1 和 v2 是必選,其餘可選
*
* @author noteless
*/
public class MyObject {
 
private int v1;//必選
private int v2;//必選
private int v3;//可選
private int v4;//可選
private int v5;//可選
private int v6;//可選
private int v7;//可選
 
private static class Builder {
 
private int v1;
private int v2;
 
private int v3 = 0;
private int v4 = 0;
private int v5 = 0;
private int v6 = 0;
private int v7 = 0;
 
public Builder(int v1, int v2) {
this.v1 = v1;
this.v2 = v2;
}
 
public Builder setV3(int v3) {
this.v3 = v3;
return this;
}
 
public Builder setV4(int v4) {
this.v4 = v4;
return this;
}
 
public Builder setV5(int v5) {
this.v5 = v5;
return this;
}
 
public Builder setV6(int v6) {
this.v6 = v6;
return this;
}
 
public Builder setV7(int v7) {
this.v7 = v7;
return this;
}
 
public MyObject build() {
return new MyObject(this);
}
}
 
private MyObject(Builder builder) {
v1 = builder.v1;
v2 = builder.v2;
v3 = builder.v3;
v4 = builder.v4;
v5 = builder.v5;
v6 = builder.v6;
v7 = builder.v7;
}
 
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("MyObject{");
sb.append("v1=").append(v1);
sb.append(", v2=").append(v2);
sb.append(", v3=").append(v3);
sb.append(", v4=").append(v4);
sb.append(", v5=").append(v5);
sb.append(", v6=").append(v6);
sb.append(", v7=").append(v7);
sb.append('}');
return sb.toString();
}
 
public static void main(String[] args) {
 
MyObject my = new MyObject.Builder(1, 2).
setV3(3).setV4(4).setV5(5).setV6(6).setV7(7).build();
System.out.println(my.toString());
}
}
省略了抽象的Builder,也省略了Director角色 示例中的Builder 就是模式中的ConcreteBuilder角色 他負責每一個步驟的實現細節,並且提供方法build()  獲取最終的產品角色物件 藉助於簡化的工廠模式進行構造方法的替換解決方案的巧妙之處在於:

public MyObject build() {

return new MyObject(this);

}

它藉助於建造者模式將實現與過程進行分離 但是在build() 方法中又並沒有嚴格的規定步驟的過程 只是在構造Builder時必須傳遞兩個必須引數,其餘的引數你可以設定,也可以不設定  達到了多層巢狀構造方法的效果 而且,還非常清晰,你不會那麼輕易地就在設定引數時犯錯,因為你需要呼叫指定的方法

總結

本文通過抽象工廠模式演化到建造者模式,看到了建造者模式與抽象工廠模式的細節差異 建造者本身並不複雜,只需要理解本意即可“複雜物件的構建過程與表示進行分離” 建造者模式是將“步驟”這一事物進行抽象化,抽象化為Builder,將事物的表示延遲到子類ConcreteBuilder中,並通過Director進行組裝  核心就是將“步驟”這一事物抽象 對於涉及到複雜物件的表示的場景,都可以考慮建造者模式 從抽象工廠的演進我們可以看得出來,建造者模式,可以藉助於抽象工廠模式進行實現