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

建造者模式 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() {web

return new MyObject(this);設計模式

}app

它藉助於建造者模式將實現與過程進行分離
可是在build() 方法中又並無嚴格的規定步驟的過程
只是在構造Builder時必須傳遞兩個必須參數,其他的參數你能夠設置,也能夠不設置 
達到了多層嵌套構造方法的效果
並且,還很是清晰,你不會那麼輕易地就在設置參數時犯錯,由於你須要調用指定的方法

總結

本文經過抽象工廠模式演化到建造者模式,看到了建造者模式與抽象工廠模式的細節差別
建造者自己並不複雜,只須要理解本意便可「複雜對象的構建過程與表示進行分離」
建造者模式是將「步驟」這一事物進行抽象化,抽象化爲Builder,將事物的表示延遲到子類ConcreteBuilder中,並經過Director進行組裝 
核心就是將「步驟」這一事物抽象
對於涉及到複雜對象的表示的場景,均可以考慮建造者模式
從抽象工廠的演進咱們能夠看得出來,建造者模式,能夠藉助於抽象工廠模式進行實現 
相關文章
相關標籤/搜索