第11章 建造者模式
11.1 變化是永恒的
又是一個周三,快要下班了,老大突然拉住我,喜滋滋地告訴我:“××公司很滿意我們做的模型,又簽訂了一個合同,把奔馳、寶馬的車輛模型都交給我們公司制作了,不過這次又額外增加了一個新需求:汽車的啟動、停止、喇叭聲音、引擎聲音都由客戶自己控制,他想什么順序就什么順序,這個沒問題吧?”
那任務又是一個時間緊、工程量大的項目,為什么是“又”呢?因為基本上每個項目都是如此,我該怎么來完成這個任務呢?
首先,我們分析一下需求,奔馳、寶馬都是一個產品,它們有共有的屬性,××公司關心的是單個模型的運行過程:奔馳模型A是先有引擎聲音,然后再響喇叭;奔馳模型B是先啟動起來,然后再有引擎聲音,這才是××公司要關心的。那到我們老大這邊呢,就是滿足人家的要求,要什么順序就立馬能產生什么順序的模型出來。我就負責把老大的要求實現出來,而且還要是批量的,也就是說××公司下單訂購寶馬A車模,我們老大馬上就找我“生產一個這樣的車模,啟動完畢后,喇叭響一下”,然后我們就準備開始批量生產這些模型。由我生產出N多個奔馳和寶馬車輛模型,這些車輛模型都有run()方法,但是具體到每一個模型的run()方法中間的執行任務的順序是不同的,老大說要啥順序,我就給啥順序,最終客戶買走后只能是既定的模型。好,需求還是比較復雜,我們先一個一個地解決,先從找一個最簡單的切入點——產品類,每個車都是一個產品,如圖11-1所示。

圖11-1 汽車模型類圖
類圖比較簡單,在CarModel中我們定義了一個setSequence方法,車輛模型的這幾個動作要如何排布,是在這個ArrayList中定義的。然后run()方法根據sequence定義的順序完成指定的順序動作,與第10章介紹的模板方法模式是不是非常類似?好,我們先看CarModel源代碼,如代碼清單11-1所示。
代碼清單11-1 車輛模型的抽象類
public?abstract?class?CarModel?{???
?????//這個參數是各個基本方法執行的順序
?????private?ArrayList<String>?sequence?=?new?ArrayList<String>();??
?????//模型是啟動開始跑了
?????protected?abstract?void?start();???
?????//能發動,還要能停下來,那才是真本事
?????protected?abstract?void?stop();????
?????//喇叭會出聲音,是滴滴叫,還是嗶嗶叫
?????protected?abstract?void?alarm();???
?????//引擎會轟隆隆地響,不響那是假的
?????protected?abstract?void?engineBoom();??????
?????//那模型應該會跑吧,別管是人推的,還是電力驅動,總之要會跑
?????final?public?void?run()?{??????????
?????????????//循環一邊,誰在前,就先執行誰
?????????????for(int?i=0;i<this.sequence.size();i++){
??????????????????????String?actionName?=?this.sequence.get(i);?????????????????
??????????????????????if(actionName.equalsIgnoreCase("start")){??
??????????????????????????????this.start();??//啟動汽車
??????????????????????}else?if(actionName.equalsIgnoreCase("stop")){?
??????????????????????????????this.stop();?//停止汽車???
??????????????????????}else?if(actionName.equalsIgnoreCase("alarm")){?
??????????????????????????????this.alarm();?//喇叭開始叫了
???????????????????????}else?if(actionName.equalsIgnoreCase("engine?boom")){
????????????????????????????????????????????????????????????//如果是engine?boom關鍵字
??????????????????????????????this.engineBoom();??//引擎開始轟鳴
???????????????????????}
?????????????}??????????
?????}??
?????//把傳遞過來的值傳遞到類內
?????final?public?void?setSequence(ArrayList?sequence){
?????????????this.sequence?=?sequence;
?????}
}
CarModel的設計原理是這樣的,setSequence方法是允許客戶自己設置一個順序,是要先啟動響一下喇叭再跑起來,還是要先響一下喇叭再啟動。對于一個具體的模型永遠都固定的,但是對N多個模型就是動態的了。在子類中實現父類的基本方法,run()方法讀取sequence,然后遍歷sequence中的字符串,哪個字符串在先,就先執行哪個方法。
兩個實現類分別實現父類的基本方法,奔馳模型如代碼清單11-2所示。
代碼清單11-2 奔馳模型代碼
public?class?BenzModel?extends?CarModel?{
?????protected?void?alarm()?{
?????????????System.out.println("奔馳車的喇叭聲音是這個樣子的...");
?????}
?????protected?void?engineBoom()?{
?????????????System.out.println("奔馳車的引擎是這個聲音的...");
?????}
?????protected?void?start()?{
?????????????System.out.println("奔馳車跑起來是這個樣子的...");
?????}
?????protected?void?stop()?{
?????????????System.out.println("奔馳車應該這樣停車...");
?????}
}
寶馬車模型如代碼清單11-3所示。
代碼清單11-3 寶馬模型代碼
public?class?BMWModel?extends?CarModel?{
?????protected?void?alarm()?{
?????????????System.out.println("寶馬車的喇叭聲音是這個樣子的...");
?????}
?????protected?void?engineBoom()?{
?????????????System.out.println("寶馬車的引擎是這個聲音的...");
?????}
?????protected?void?start()?{
?????????????System.out.println("寶馬車跑起來是這個樣子的...");
?????}
?????protected?void?stop()?{
?????????????System.out.println("寶馬車應該這樣停車...");
?????}
}
兩個產品的實現類都完成,我們來模擬一下××公司的要求:生產一個奔馳模型,要求跑的時候,先發動引擎,然后再掛擋啟動,然后停下來,不需要喇叭。這個需求很容易滿足,我們增加一個場景類實現該需求,如代碼清單11-4所示。
代碼清單11-4 奔馳模型代碼
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????/*
??????????????*?客戶告訴XX公司,我要這樣一個模型,然后XX公司就告訴我老大
??????????????*?說要這樣一個模型,這樣一個順序,然后我就來制造
??????????????*/
?????????????BenzModel?benz?=?new?BenzModel();
?????????????//存放run的順序
?????????????ArrayList<String>?sequence??=?new?ArrayList<String>();?????????????????
?????????????sequence.add("engine?boom");??//客戶要求,run的時候先發動引擎
?????????????sequence.add("start");??//啟動起來
?????????????sequence.add("stop");???//開了一段就停下來?????????
?????????????//我們把這個順序賦予奔馳車
?????????????benz.setSequence(sequence);
?????????????benz.run();????????????????
?????}
}
運行結果如下所示:
奔馳車的引擎是這個聲音的...
奔馳車跑起來是這個樣子的...
奔馳車應該這樣停車...
看,我們組裝了這樣的一輛汽車,滿足了××公司的需求。但是想想我們的需求,汽車的動作執行順序是要能夠隨意調整的。我們只滿足了一個需求,還有下一個需求呀,然后是第二個寶馬模型,只要啟動、停止,其他的什么都不要;第三個模型,先喇叭,然后啟動,然后停止;第四個……直到把你逼瘋為止,那怎么辦?我們就一個一個地來寫場景類滿足嗎?不可能了,那我們要想辦法來解決這個問題,有了!我們為每種模型產品模型定義一個建造者,你要啥順序直接告訴建造者,由建造者來建造,于是乎我們就有了如圖11-2所示的類圖。

圖11-2 增加了建造者的汽車模型類圖
增加了一個CarBuilder抽象類,由它來組裝各個車模,要什么類型什么順序的車輛模型,都由相關的子類完成。首先編寫CarBuilder代碼,如代碼清單11-5所示。
代碼清單11-5 抽象汽車組裝者
public?abstract?class?CarBuilder?{
?????//建造一個模型,你要給我一個順序要求,就是組裝順序
?????public?abstract?void?setSequence(ArrayList<String>?sequence);
?????//設置完畢順序后,就可以直接拿到這個車輛模型
?????public?abstract?CarModel?getCarModel();
}
很簡單,每個車輛模型都要有確定的運行順序,然后才能返回一個車輛模型。奔馳車的組裝者如代碼清單11-6所示。
代碼清單11-6 奔馳車組裝者
public?class?BenzBuilder?extends?CarBuilder?{
?????private?BenzModel?benz?=?new?BenzModel();
?????public?CarModel?getCarModel()?{
?????????????return?this.benz;
?????}
?????public?void?setSequence(ArrayList<String>?sequence)?{
?????????????this.benz.setSequence(sequence);
?????}
}
非常簡單實用的程序,給定一個汽車的運行順序,然后就返回一個奔馳車,簡單了很多。寶馬車的組裝與此相同,如代碼清單11-7所示。
代碼清單11-7 寶馬車組裝者
public?class?BMWBuilder?extends?CarBuilder?{
?????private?BMWModel?bmw?=?new?BMWModel();
?????public?CarModel?getCarModel()?{
?????????????return?this.bmw;
?????}
?????public?void?setSequence(ArrayList<String>?sequence)?{
?????????????this.bmw.setSequence(sequence);
?????}
}
兩個組裝者都完成了,我們再來看看××公司的需求如何滿足,修改一下場景類,如代碼清單11-8所示。
代碼清單11-8 修改后的場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????/*
??????????????*?客戶告訴XX公司,我要這樣一個模型,然后XX公司就告訴我老大
??????????????*?說要這樣一個模型,這樣一個順序,然后我就來制造
??????????????*/
?????????????//存放run的順序
?????????????ArrayList<String>?sequence?=?new?ArrayList<String>();
?????????????sequence.add("engine?boom");??//客戶要求,run時候時候先發動引擎
?????????????sequence.add("start");??//啟動起來
?????????????sequence.add("stop");???//開了一段就停下來
?????????????//要一個奔馳車:
?????????????BenzBuilder?benzBuilder?=?new?BenzBuilder();
?????????????//把順序給這個builder類,制造出這樣一個車出來
?????????????benzBuilder.setSequence(sequence);
?????????????//制造出一個奔馳車
?????????????BenzModel?benz?=?(BenzModel)benzBuilder.getCarModel();
?????????????//奔馳車跑一下看看
?????????????benz.run();????????????????
?????}
}
運行結果如下所示:
奔馳車的引擎是這個聲音的...
奔馳車跑起來是這個樣子的...
奔馳車應該這樣停車...
那如果我再想要個同樣順序的寶馬車呢?很簡單,再次修改一下場景類,如代碼清單11-9所示。
代碼清單11-9 相同順序的寶馬車的場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//存放run的順序
?????????????ArrayList<String>?sequence?=?new?ArrayList<String>();
?????????????sequence.add("engine?boom");??//客戶要求,run的時候先發動引擎
?????????????sequence.add("start");??//啟動起來
?????????????sequence.add("stop");??//開了一段就停下來
?????????????//要一個奔馳車:
?????????????BenzBuilder?benzBuilder?=?new?BenzBuilder();
?????????????//把順序給這個builder類,制造出這樣一個車出來
?????????????benzBuilder.setSequence(sequence);
?????????????//制造出一個奔馳車
?????????????BenzModel?benz?=?(BenzModel)benzBuilder.getCarModel();
?????????????//奔馳車跑一下看看
?????????????benz.run();
?????????????//按照同樣的順序,我再要一個寶馬
?????????????BMWBuilder?bmwBuilder?=?new?BMWBuilder();
?????????????bmwBuilder.setSequence(sequence);
?????????????BMWModel?bmw?=?(BMWModel)bmwBuilder.getCarModel();
?????????????bmw.run();?????????????????
?????}
}
運行結果如下所示:
奔馳車的引擎是這個聲音的...
奔馳車跑起來是這個樣子的...
奔馳車應該這樣停車...
寶馬車的引擎是這個聲音的...
寶馬車跑起來是這個樣子的...
寶馬車應該這樣停車...
看,同樣運行順序的寶馬車也生產出來了,而且代碼是不是比剛開始直接訪問產品類(Procuct)簡單了很多。我們在做項目時,經常會有一個共識:需求是無底洞,是無理性的,不可能你告訴它不增加需求就不增加,這4個過程(start、stop、alarm、engine boom)按照排列組合有很多種,××公司可以隨意組合,它要什么順序的車模我就必須生成什么順序的車模,客戶可是上帝!那我們不可能預知他們要什么順序的模型呀,怎么辦?封裝一下,找一個導演,指揮各個事件的先后順序,然后為每種順序指定一個代碼,你說一種我們立刻就給你生產處理,好方法,厲害!我們先修改一下類圖,如圖11-3所示。

圖11-3 完整汽車模型類圖
類圖看著復雜了,但還是比較簡單,我們增加了一個Director類,負責按照指定的順序生產模型,其中方法說明如下:
● getABenzModel方法
組建出A型號的奔馳車輛模型,其過程為只有啟動(start)、停止(stop)方法,其他的引擎聲音、喇叭都沒有。
● getBBenzModel方法
組建出B型號的奔馳車,其過程為先發動引擎(engine boom),然后啟動,再然后停車,沒有喇叭。
● getCBMWModel方法
組建出C型號的寶馬車,其過程為先喇叭叫一下(alarm),然后啟動,再然后是停車,引擎不轟鳴。
● getDBMWModel方法
組建出D型號的寶馬車,其過程就一個啟動,然后一路跑到黑,永動機,沒有停止方法,沒有喇叭,沒有引擎轟鳴。
其他的E型號、F型號……可以有很多,啟動、停止、喇叭、引擎轟鳴這4個方法在這個類中可以隨意地自由組合。Director類如代碼清單11-10所示。
代碼清單11-10 導演類
public?class?Director?{
?????private?ArrayList<String>?sequence?=?new?ArrayList();
?????private?BenzBuilder?benzBuilder?=?new?BenzBuilder();
?????private?BMWBuilder?bmwBuilder?=?new?BMWBuilder();
?????/*
??????*?A類型的奔馳車模型,先start,然后stop,其他什么引擎、喇叭一概沒有
??????*/
?????public?BenzModel?getABenzModel(){
?????????????//清理場景,這里是一些初級程序員不注意的地方
?????????????this.sequence.clear();
?????????????//ABenzModel的執行順序
?????????????this.sequence.add("start");
?????????????this.sequence.add("stop");
?????????????//按照順序返回一個奔馳車
?????????????this.benzBuilder.setSequence(this.sequence);
?????????????return?(BenzModel)this.benzBuilder.getCarModel();
?????}
?????/*
??????*?B型號的奔馳車模型,是先發動引擎,然后啟動,然后停止,沒有喇叭
??????*/
?????public?BenzModel?getBBenzModel(){
?????????????this.sequence.clear();
?????????????this.sequence.add("engine?boom");
?????????????this.sequence.add("start");
?????????????this.sequence.add("stop");
?????????????this.benzBuilder.setSequence(this.sequence);
?????????????return?(BenzModel)this.benzBuilder.getCarModel();
?????}
?????/*
??????*?C型號的寶馬車是先按下喇叭(炫耀嘛),然后啟動,然后停止
??????*/
?????public?BMWModel?getCBMWModel(){
?????????????this.sequence.clear();
?????????????this.sequence.add("alarm");
?????????????this.sequence.add("start");
?????????????this.sequence.add("stop");
?????????????this.bmwBuilder.setSequence(this.sequence);
?????????????return?(BMWModel)this.bmwBuilder.getCarModel();
?????}
?????/*
??????*?D類型的寶馬車只有一個功能,就是跑,啟動起來就跑,永遠不停止
??????*/
?????public?BMWModel?getDBMWModel(){
?????????????this.sequence.clear();
?????????????this.sequence.add("start");
?????????????this.bmwBuilder.setSequence(this.sequence);
?????????????return?(BMWModel)this.benzBuilder.getCarModel();
?????}
?????/*
??????*?這里還可以有很多方法,你可以先停止,然后再啟動,或者一直停著不動,靜態的嘛
??????*?導演類嘛,按照什么順序是導演說了算
??????*/
}
順便說一下,大家看一下程序中有很多this調用。這個我一般是這樣要求項目組成員的,如果你要調用類中的成員變量或方法,需要在前面加上this關鍵字,不加也能正常地跑起來,但是不清晰,加上this關鍵字,我就是要調用本類中的成員變量或方法,而不是本方法中的一個變量。還有super方法也是一樣,是調用父類的成員變量或者方法,那就加上這個關鍵字,不要省略,這要靠約束,還有就是程序員的自覺性,他要是死不悔改,那咱也沒招。
注意 上面每個方法都有一個this.sequence.clear(),估計你一看就明白。但是作為一個系統分析師或是技術經理一定要告訴項目成員,ArrayList和HashMap如果定義成類的成員變量,那你在方法中的調用一定要做一個clear的動作,以防止數據混亂。如果你發生過一次類似問題的話,比如ArrayList中出現一個“出乎意料”的數據,而你又花費了幾個通宵才解決這個問題,那你會有很深刻的印象。
有了這樣一個導演類后,我們的場景類就更容易處理了,××公司要A類型的奔馳車1萬輛,B類型的奔馳車100萬輛,C類型的寶馬車1000萬輛,D類型的不需要,非常容易處理,如代碼清單11-11所示。
代碼清單11-11 導演類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????Director?director?=?new?Director();????????????????
?????????????//1萬輛A類型的奔馳車
?????????????for(int?i=0;i<10000;i++){
?????????????????????director.getABenzModel().run();
?????????????}??????????
?????????????//100萬輛B類型的奔馳車
?????????????for(int?i=0;i<1000000;i++){
?????????????????????director.getBBenzModel().run();
?????????????}??????????
?????????????//1000萬輛C類型的寶馬車
?????????????for(int?i=0;i<10000000;i++){
?????????????????????director.getCBMWModel().run();
?????????????}
?????}
}
清晰、簡單吧,我們寫程序重構的最終目的就是:簡單、清晰。代碼是讓人看的,不是寫完就完事了,我一直在教育我帶的團隊成員,Java程序不是像我們前輩寫二進制代碼、匯編一樣,寫完基本上就自己能看懂,別人看就跟看天書一樣,現在的高級語言,要像寫中文漢字一樣,你寫的,別人能看懂。這就是建造者模式。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——6大設計原則全新解讀
- 第1章 單一職責原則
- 1.2 絕殺技,打破你的傳統思維
- 1.3 我單純,所以我快樂
- 1.4 最佳實踐
- 第2章 里氏替換原則
- 2.2 糾紛不斷,規則壓制
- 2.3 最佳實踐
- 第3章 依賴倒置原則
- 3.2 言而無信,你太需要契約
- 3.3 依賴的三種寫法
- 3.4 最佳實踐
- 第4章 接口隔離原則
- 4.2 美女何其多,觀點各不同
- 4.3 保證接口的純潔性
- 4.4 最佳實踐
- 第5章 迪米特法則
- 5.2 我的知識你知道得越少越好
- 5.3 最佳實踐
- 第6章 開閉原則
- 6.2 開閉原則的廬山真面目
- 6.3 為什么要采用開閉原則
- 6.4 如何使用開閉原則
- 6.5 最佳實踐
- 第二部分 真刀實槍 ——23種設計模式完美演繹
- 第7章 單例模式
- 7.2 單例模式的定義
- 7.3 單例模式的應用
- 7.4 單例模式的擴展
- 7.5 最佳實踐
- 第8章 工廠方法模式
- 8.2 工廠方法模式的定義
- 8.3 工廠方法模式的應用
- 8.4 工廠方法模式的擴展
- 8.5 最佳實踐
- 第9章 抽象工廠模式
- 9.2 抽象工廠模式的定義
- 9.3 抽象工廠模式的應用
- 9.4 最佳實踐
- 第10章 模板方法模式
- 10.2 模板方法模式的定義
- 10.3 模板方法模式的應用
- 10.4 模板方法模式的擴展
- 10.5 最佳實踐
- 第11章 建造者模式
- 11.2 建造者模式的定義
- 11.3 建造者模式的應用
- 11.4 建造者模式的擴展
- 11.5 最佳實踐
- 第12章 代理模式
- 12.2 代理模式的定義
- 12.3 代理模式的應用
- 12.4 代理模式的擴展
- 12.5 最佳實踐
- 第13章 原型模式
- 13.2 原型模式的定義
- 13.3 原型模式的應用
- 13.4 原型模式的注意事項
- 13.5 最佳實踐
- 第14章 中介者模式
- 14.2 中介者模式的定義
- 14.3 中介者模式的應用
- 14.4 中介者模式的實際應用
- 14.5 最佳實踐
- 第15章 命令模式
- 15.2 命令模式的定義
- 15.3 命令模式的應用
- 15.4 命令模式的擴展
- 15.5 最佳實踐
- 第16章 責任鏈模式
- 16.2 責任鏈模式的定義
- 16.3 責任鏈模式的應用
- 16.4 最佳實踐
- 第17章 裝飾模式
- 17.2 裝飾模式的定義
- 17.3 裝飾模式應用
- 17.4 最佳實踐
- 第18章 策略模式
- 18.2 策略模式的定義
- 18.3 策略模式的應用
- 18.4 策略模式的擴展
- 18.5 最佳實踐
- 第19章 適配器模式
- 19.2 適配器模式的定義
- 19.3 適配器模式的應用
- 19.4 適配器模式的擴展
- 19.5 最佳實踐
- 第20章 迭代器模式
- 20.2 迭代器模式的定義
- 20.3 迭代器模式的應用
- 20.4 最佳實踐
- 第21章 組合模式
- 21.2 組合模式的定義
- 21.3 組合模式的應用
- 21.4 組合模式的擴展
- 21.5 最佳實踐
- 第22章 觀察者模式
- 22.2 觀察者模式的定義
- 22.3 觀察者模式的應用
- 22.4 觀察者模式的擴展
- 22.5 最佳實踐
- 第23章 門面模式
- 23.2 門面模式的定義
- 23.3 門面模式的應用
- 23.4 門面模式的注意事項
- 23.5 最佳實踐
- 第24章 備忘錄模式
- 24.2 備忘錄模式的定義
- 24.3 備忘錄模式的應用
- 24.4 備忘錄模式的擴展
- 24.5 最佳實踐
- 第25章 訪問者模式
- 25.2 訪問者模式的定義
- 25.3 訪問者模式的應用
- 25.4 訪問者模式的擴展
- 25.5 最佳實踐
- 第26章 狀態模式
- 26.2 狀態模式的定義
- 26.3 狀態模式的應用
- 第27章 解釋器模式
- 27.2 解釋器模式的定義
- 27.3 解釋器模式的應用
- 27.4 最佳實踐
- 第28章 享元模式
- 28.2 享元模式的定義
- 28.3 享元模式的應用
- 28.4 享元模式的擴展
- 28.5 最佳實踐
- 第29章 橋梁模式
- 29.2 橋梁模式的定義
- 29.3 橋梁模式的應用
- 29.4 最佳實踐
- 第三部分 誰的地盤誰做主 ——設計模式PK
- 第30章 創建類模式大PK
- 30.1 工廠方法模式VS建造者模式
- 30.2 抽象工廠模式VS建造者模式
- 第31章 結構類模式大PK
- 31.1 代理模式VS裝飾模式
- 31.2 裝飾模式VS適配器模式
- 第32章 行為類模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS狀態模式
- 32.3 觀察者模式VS責任鏈模式
- 第33章 跨戰區PK
- 33.1 策略模式VS橋梁模式
- 33.2 門面模式VS中介者模式
- 33.3 包裝模式群PK
- 第四部分 完美世界 ——設計模式混編
- 第34章 命令模式+責任鏈模式
- 34.2 混編小結
- 第35章 工廠方法模式+策略模式
- 35.2 混編小結
- 第36章 觀察者模式+中介者模式
- 36.2 混編小結
- 第五部分 擴展篇
- 第37章 MVC框架
- 37.2 最佳實踐
- 第38章 新模式
- 38.1 規格模式
- 38.2 對象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空對象模式
- 附錄 23種設計模式彩圖