## 引入
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述建造(Builder)模式的:
> 建造模式是對象的創建模式。建造模式可以將一個產品的內部表象(internal representation)與產品的生產過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品對象。
**產品的內部表象**
一個產品常有不同的組成成分作為產品的零件,這些零件有可能是對象,也有可能不是對象,它們通常又叫做產品的內部表象(internal representation)。不同的產品可以有不同的內部表象,也就是不同的零件。使用建造模式可以使客戶端不需要知道所生成的產品有哪些零件,每個產品的對應零件彼此有何不同,是怎么建造出來的,以及怎么組成產品。
**對象性質的建造**
有些情況下,一個對象會有一些重要的性質,在它們沒有恰當的值之前,對象不能作為一個完整的產品使用。比如,一個電子郵件有發件人地址、收件人地址、主題、內容、附錄等部分,而在最起碼的收件人地址得到賦值之前,這個電子郵件不能發送。
有些情況下,一個對象的一些性質必須按照某個順序賦值才有意義。在某個性質沒有賦值之前,另一個性質則無法賦值。這些情況使得性質本身的建造涉及到復雜的商業邏輯。這時候,此對象相當于一個有待建造的產品,而對象的這些性質相當于產品的零件,建造產品的過程是建造零件的過程。由于建造零件的過程很復雜,因此,這些零件的建造過程往往被“外部化”到另一個稱做建造者的對象里,建造者對象返還給客戶端的是一個全部零件都建造完畢的產品對象。
建造模式利用一個導演者對象和具體建造者對象一個個地建造出所有的零件,從而建造出完整的產品對象。建造者模式將產品的結構和產品的零件的建造過程對客戶端隱藏起來,把對建造過程進行指揮的責任和具體建造者零件的責任分割開來,達到責任劃分和封裝的目的。
## 模式結構
建造者模式的UML結構圖:

四個角色,它們分別是:
* 抽象建造者(Builder)角色:給 出一個抽象接口,以規范產品對象的各個組成成分的建造。一般而言,此接口獨立于應用程序的商業邏輯。模式中直接創建產品對象的是具體建造者 (ConcreteBuilder)角色。具體建造者類必須實現這個接口所要求的兩種方法:一種是建造方法(buildPart1和 buildPart2),另一種是返還結構方法(retrieveResult)。一般來說,產品所包含的零件數目與建造方法的數目相符。換言之,有多少 零件,就有多少相應的建造方法。
* 具體建造者(ConcreteBuilder)角色:擔任這個角色的是與應用程序緊密相關的一些類,它們在應用程序調用下創建產品的實例。這個角色要完成的任務包括:1.實現抽象建造者Builder所聲明的接口,給出一步一步地完成創建產品實例的操作。2.在建造過程完成后,提供產品的實例。
* 導演者(Director)角色:擔任這個角色的類調用具體建造者角色以創建產品對象。應當指出的是,導演者角色并沒有產品類的具體知識,真正擁有產品類的具體知識的是具體建造者角色。
* 產品(Product)角色:產品便是建造中的復雜對象。一般來說,一個系統中會有多于一個的產品類,而且這些產品類并不一定有共同的接口,而完全可以是不相關聯的。
## 代碼實現
導演者角色是與客戶端打交道的角色。導演者將客戶端創建產品的請求劃分為對各個零件的建造請求,再將這些請求委派給具體建造者角色。具體建造者角色是做具體建造工作的,但是卻不為客戶端所知。
一般來說,每有一個產品類,就有一個相應的具體建造者類。這些產品應當有一樣數目的零件,而每有一個零件就相應地在所有的建造者角色里有一個建造方法。
KFC里面一般都有好幾種可供客戶選擇的套餐,它可以根據客戶所點的套餐,然后在后面做這些套餐,返回給客戶的事一個完整的、美好的套餐。下面我們將會模擬這個過程,我們約定套餐主要包含漢堡、薯條、可樂、雞腿等等組成部分,使用不同的組成部分就可以構建出不同的套餐。

首先是套餐類:Meal.java
~~~
public class Meal {
private String food;
private String drink;
public String getFood() {
return food;
}
public void setFood(String food) {
this.food = food;
}
public String getDrink() {
return drink;
}
public void setDrink(String drink) {
this.drink = drink;
}
}
~~~
套餐構造器:MealBuilder.java
~~~
public abstract class MealBuilder {
Meal meal = new Meal();
public abstract void buildFood();
public abstract void buildDrink();
public Meal getMeal(){
return meal;
}
}
~~~
然后是套餐A、套餐B。這個兩個套餐都是實現抽象套餐類。
~~~
public class MealA extends MealBuilder{
public void buildDrink() {
meal.setDrink("一杯可樂");
}
public void buildFood() {
meal.setFood("一盒薯條");
}
}
~~~
~~~
public class MealB extends MealBuilder{
public void buildDrink() {
meal.setDrink("一杯檸檬果汁");
}
public void buildFood() {
meal.setFood("三個雞翅");
}
}
~~~
最后是KFC的服務員,它相當于一個指揮者,它決定了套餐是的實現過程,然后給你一個完美的套餐。
~~~
public class KFCWaiter {
private MealBuilder mealBuilder;
public void setMealBuilder(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
public Meal construct(){
//準備食物
mealBuilder.buildFood();
//準備飲料
mealBuilder.buildDrink();
//準備完畢,返回一個完整的套餐給客戶
return mealBuilder.getMeal();
}
}
~~~
測試類
~~~
public class Client {
public static void main(String[] args) {
//服務員
KFCWaiter waiter = new KFCWaiter();
//套餐A
MealA a = new MealA();
//服務員準備套餐A
waiter.setMealBuilder(a);
//獲得套餐
Meal mealA = waiter.construct();
System.out.print("套餐A的組成部分:");
System.out.println(mealA.getFood()+"---"+mealA.getDrink());
}
}
~~~
**使用建造模式構建復雜對象**
建造模式分成兩個很重要的部分:
1. 一個部分是Builder接口,這里是定義了如何構建各個部件,也就是知道每個部件功能如何實現,以及如何裝配這些部件到產品中去;
2. 另外一個部分是Director,Director是知道如何組合來構建產品,也就是說Director負責整體的構建算法,而且通常是分步驟地來執行。
不管如何變化,建造模式都存在這么兩個部分,一個部分是部件構造和產品裝配,另一個部分是整體構建的算法。認識這點是很重要的,因為在建造模式中,強調的是固定整體構建的算法,而靈活擴展和切換部件的具體構造和產品裝配的方式。
再直白點說,建造模式的重心在于分離構建算法和具體的構造實現,從而使得構建算法可以重用。具體的構造實現可以很方便地擴展和切換,從而可以靈活地組合來構造出不同的產品對象。
考慮這樣一個實際應用,要創建一個保險合同的對象,里面很多屬性的值都有約束,要求創建出來的對象是滿足這些約束規則的。約束規則比如:保險合同通常情況下可以和個人簽訂,也可以和某個公司簽訂,但是一份保險合同不能同時與個人和公司簽訂。這個對象里有很多類似這樣的約束,采用建造模式來構建復雜的對象,通常會對建造模式進行一定的簡化,因為目標明確,就是創建某個復雜對象,因此做適當簡化會使程序更簡潔。大致簡化如下:
● 由于是用Builder模式來創建某個對象,因此就沒有必要再定義一個Builder接口,直接提供一個具體的建造者類就可以了。
● 對于創建一個復雜的對象,可能會有很多種不同的選擇和步驟,干脆去掉“導演者”,把導演者的功能和Client的功能合并起來,也就是說,Client這個時候就相當于導演者,它來指導構建器類去構建需要的復雜對象。
保險合同類
~~~
/**
* 保險合同對象
*/
public class InsuranceContract {
//保險合同編號
private String contractId;
/**
* 被保險人員的名稱,同一份保險合同,要么跟人員簽訂,要么跟公司簽訂
* 也就是說,“被保險人員”和“被保險公司”這兩個屬性,不可能同時有值
*/
private String personName;
//被保險公司的名稱
private String companyName;
//保險開始生效日期
private long beginDate;
//保險失效日期,一定會大于保險開始生效日期
private long endDate;
//其他數據
private String otherData;
//私有構造方法
private InsuranceContract(ConcreteBuilder builder){
this.contractId = builder.contractId;
this.personName = builder.personName;
this.companyName = builder.companyName;
this.beginDate = builder.beginDate;
this.endDate = builder.endDate;
this.otherData = builder.otherData;
}
/**
* 保險合同的一些操作
*/
public void someOperation(){
System.out.println("當前正在操作的保險合同編號為【"+this.contractId+"】");
}
public static class ConcreteBuilder{
private String contractId;
private String personName;
private String companyName;
private long beginDate;
private long endDate;
private String otherData;
/**
* 構造方法,傳入必須要有的參數
* @param contractId 保險合同編號
* @param beginDate 保險合同開始生效日期
* @param endDate 保險合同失效日期
*/
public ConcreteBuilder(String contractId,long beginDate,long endDate){
this.contractId = contractId;
this.beginDate = beginDate;
this.endDate = endDate;
}
//被保險人員的名稱
public ConcreteBuilder setPersonName(String personName) {
this.personName = personName;
return this;
}
//被保險公司的名稱
public ConcreteBuilder setCompanyName(String companyName) {
this.companyName = companyName;
return this;
}
//其他數據
public ConcreteBuilder setOtherData(String otherData) {
this.otherData = otherData;
return this;
}
/**
* 構建真正的對象并返回
* @return 構建的保險合同對象
*/
public InsuranceContract build(){
if(contractId == null || contractId.trim().length()==0){
throw new IllegalArgumentException("合同編號不能為空");
}
boolean signPerson = (personName != null && personName.trim().length() > 0);
boolean signCompany = (companyName != null && companyName.trim().length() > 0);
if(signPerson && signCompany){
throw new IllegalArgumentException("一份保險合同不能同時與個人和公司簽訂");
}
if(signPerson == false && signCompany == false){
throw new IllegalArgumentException("一份保險合同不能沒有簽訂對象");
}
if(beginDate <= 0 ){
throw new IllegalArgumentException("一份保險合同必須有開始生效的日期");
}
if(endDate <=0){
throw new IllegalArgumentException("一份保險合同必須有失效的日期");
}
if(endDate < beginDate){
throw new IllegalArgumentException("一份保險合同的失效日期必須大于生效日期");
}
return new InsuranceContract(this);
}
}
}
~~~
客戶端類
~~~
public class Client {
public static void main(String[]args){
//創建構建器對象
InsuranceContract.ConcreteBuilder builder =
new InsuranceContract.ConcreteBuilder("9527", 123L, 456L);
//設置需要的數據,然后構建保險合同對象
InsuranceContract contract =
builder.setPersonName("小明").setOtherData("test").build();
//操作保險合同對象的方法
contract.someOperation();
}
}
~~~
在本例中將具體建造者合并到了產品對象中,并將產品對象的構造函數私有化,防止客戶端不使用構建器來構建產品對象,而是直接去使用new來構建產品對象所導致的問題。另外,這個構建器的功能就是為了創建被構建的對象,完全可以不用單獨一個類。
## 優點
* 1、將復雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,使得我們能夠更加精確的控制復雜對象的產生過程。
* 2、將產品的創建過程與產品本身分離開來,可以使用相同的創建過程來得到不同的產品。也就說細節依賴抽象。
* 3、每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象。
## 缺點
* 1、建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,則不適合使用建造者模式,因此其使用范圍受到一定的限制。
* 2、如果產品的內部變化復雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大。
## 建造者模式和抽象工廠模式的區別
在抽象工廠樣式中,每一次工廠對象被呼叫時都會傳回一個**完整的產品對象**,而使用端有可能會決定把這些產品組裝成一個更大的和復雜的產品,也有可能不會。工廠對象是沒有狀態的,不知道上一次構建的是哪一個產品,也沒有未來的概念,不知道下一次構建的是哪一個產品,更不知道自己構建的產品在更高層的產品結構藍圖中是什么位置。
建造類別則不同,建造樣式的重點在導演者角色。**導演者對象是有狀態的**,它知道整體藍圖,知道上一次、這一次和下一次交給建造者角色去構建的零件是什么,以便能夠將這些零件組裝成為一個更大規模的產品。它一點一點地建造出一個復雜的產品,而這個產品的組裝程序就發生在導演者角色內部。建造者樣式的使用端拿到的是一個完整的最后產品。
換言之,雖然抽象工廠樣式與建造樣式都是設計樣式,但是抽象工廠樣式處在更加具體的尺度上,而建造樣式則處于更加宏觀的尺度上。一個系統可以由一個建造樣式和一個抽象工廠樣式組成,使用端通過呼叫這個導演角色,間接地呼叫另一個抽象工廠樣式的工廠角色。工廠樣式傳回不同產品族的零件,而建造者樣式則把它們組裝起來。
比如仍以眾神造人為例,女媧利用建造樣式負責把靈魂、耳目、手臂等組合成一個完整的人,而黃帝、上駢、桑林各自利用工廠樣式創造出靈魂、耳目、臂手等。女媧不必考慮靈魂、耳目、手臂是什么樣子、怎么創造出來的,這就成為一個由建造樣式和抽象工廠樣式組合而成的系統。
## 適用場景
* 1、需要生成的產品對象有復雜的內部結構,每一個內部成分本身可以是對象,也可以僅僅是一個對象(即產品對象)的一個組成部分。
* 2、需要生成的產品對象的屬性相互依賴。建造模式可以強制實行一種分步驟進行的建造過程,因此,如果產品對象的一個屬性必須在另一個屬性被賦值之后才可以被賦值,使用建造模式是一個很好的設計思想。
* 3、在對象創建過程中會使用到系統中的其他一些對象,這些對象在產品對象的創建過程中不易得到。
## 總結
* 1、建造者模式是將一個復雜對象的創建過程給封裝起來,客戶只需要知道可以利用對象名或者類型就能夠得到一個完整的對象實例,而不需要關心對象的具體創建過程。
* 2、建造者模式將對象的創建過程與對象本身隔離開了,使得細節依賴于抽象,符合依賴倒置原則。可以使用相同的創建過程來創建不同的產品對象。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux