建造模式是對象的創建模式。建造模式可以將一個產品的內部表象(internal representation)與產品的生產過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品對象。
產品的內部表象
一個產品常有不同的組成成分作為產品的零件,這些零件有可能是對象,也有可能不是對象,它們通常又叫做產品的內部表象(internal representation)。不同的產品可以有不同的內部表象,也就是不同的零件。使用建造模式可以使客戶端不需要知道所生成的產品有哪些零件,每個產品的對應零件彼此有何不同,是怎么建造出來的,以及怎么組成產品。
對象性質的建造
有些情況下,一個對象會有一些重要的性質,在它們沒有恰當的值之前,對象不能作為一個完整的產品使用。比如,一個電子郵件有發件人地址、收件人地址、主題、內容、附錄等部分,而在最起碼的收件人地址得到賦值之前,這個電子郵件不能發送。
有些情況下,一個對象的一些性質必須按照某個順序賦值才有意義。在某個性質沒有賦值之前,另一個性質則無法賦值。這些情況使得性質本身的建造涉及到復雜的商業邏輯。這時候,此對象相當于一個有待建造的產品,而對象的這些性質相當于產品的零件,建造產品的過程是建造零件的過程。由于建造零件的過程很復雜,因此,這些零件的建造過程往往被“外部化”到另一個稱做建造者的對象里,建造者對象返還給客戶端的是一個全部零件都建造完畢的產品對象。
建造模式利用一個導演者對象和具體建造者對象一個個地建造出所有的零件,從而建造出完整的產品對象。建造者模式將產品的結構和產品的零件的建造過程對客戶端隱藏起來,把對建造過程進行指揮的責任和具體建造者零件的責任分割開來,達到責任劃分和封裝的目的。
建造模式的結構
在這個示意性的系統里,最終產品Product只有兩個零件,即part1和part2。相應的建造方法也有兩個:buildPart1()和buildPart2()、同時可以看出本模式涉及到四個角色,它們分別是:
1.抽象建造者(Builder)角色:
給出一個抽象接口,以規范產品對象的各個組成成分的建造。一般而言,此接口獨立于應用程序的商業邏輯。模式中直接創建產品對象的是具體建造者 (ConcreteBuilder)角色。具體建造者類必須實現這個接口所要求的兩種方法:一種是建造方法(buildPart1和 buildPart2),另一種是返還結構方法(retrieveResult)。一般來說,產品所包含的零件數目與建造方法的數目相符。換言之,有多少 零件,就有多少相應的建造方法。
2.具體建造者(ConcreteBuilder)角色:
擔任這個角色的是與應用程序緊密相關的一些類,它們在應用程序調用下創建產品的實例。這個角色要完成的任務包括:
- 實現抽象建造者Builder所聲明的接口,給出一步一步地完成創建產品實例的操作。
- 在建造過程完成后,提供產品的實例。
3.導演者(Director)角色:
擔任這個角色的類調用具體建造者角色以創建產品對象。應當指出的是,導演者角色并沒有產品類的具體知識,真正擁有產品類的具體知識的是具體建造者角色。
4.產品(Product)角色:
產品便是建造中的復雜對象。一般來說,一個系統中會有多于一個的產品類,而且這些產品類并不一定有共同的接口,而完全可以是不相關聯的。
導演者角色是與客戶端打交道的角色。導演者將客戶端創建產品的請求劃分為對各個零件的建造請求,再將這些請求委派給具體建造者角色。具體建造者角色是做具體建造工作的,但是卻不為客戶端所知。
一般來說,每有一個產品類,就有一個相應的具體建造者類。這些產品應當有一樣數目的零件,而每有一個零件就相應地在所有的建造者角色里有一個建造方法。
源代碼
/**
* 產品類
*/
public class Product {
//定義一些產品的零件
private String part1;
private String part2;
public String getPart1() {
return part1;
}
public void setPart1(String part1) {
this.part1 = part1;
}
public String getPart2() {
return part2;
}
public void setPart2(String part2) {
this.part2 = part2;
}
}
/**
* 建造者接口,定義了建造產品需要實現的方法
*/
public interface Builder {
/**
* 創建產品的第一部分
*/
public void buildPart1();
/**
* 創建產品的第二部分
*/
public void buildPart2();
/**
* 返回產品
*/
public Product getResult();
}
/**
* 具體的建造者對象
*/
public class ConcreteBuilder1 implements Builder{
private Product product;
public ConcreteBuilder1() {
this.product = new Product();
}
/**
* 產品零件建造方法1
*/
@Override
public void buildPart1() {
this.product.setPart1("part1");
}
/**
* 產品零件建造方法2
*/
@Override
public void buildPart2() {
this.product.setPart2("part2");
}
/**
* 產品返還方法
*/
@Override
public Product getResult() {
return this.product;
}
}
/**
* 導演,指揮者,調用具體建造者完成產品的建造
*/
public class Director {
/**
* 持有當前需要使用的建造器對象
*/
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
/**
* 產品建造方法,完成產品各個零件的建造
*/
public void construct(){
builder.buildPart1();
builder.buildPart2();
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder1();
Director director = new Director(builder);
director.construct();
Product product = builder.getResult();
System.out.println(product.getPart1());
System.out.println(product.getPart2());
}
}
流程分析
客戶端負責創建導演者和具體建造者對象。然后,客戶端把具體建造者對象交給導演者,導演者操作具體建造者,開始創建產品。當產品完成后,建造者把產品返還給客戶端。
把創建具體建造者對象的任務交給客戶端而不是導演者對象,是為了將導演者對象與具體建造者對象的耦合變成動態的,從而使導演者對象可以操縱數個具體建造者對象中的任何一個。
建造模式分成兩個很重要的部分:
1. 一個部分是Builder接口,這里是定義了如何構建各個部件,也就是知道每個部件功能如何實現,以及如何裝配這些部件到產品中去;
2. 另外一個部分是Director,Director是知道如何組合來構建產品,也就是說Director負責整體的構建算法,而且通常是分步驟地來執行。
不管如何變化,建造模式都存在這么兩個部分,一個部分是部件構造和產品裝配,另一個部分是整體構建的算法。認識這點是很重要的,因為在建造模式中,強調的是固定整體構建的算法,而靈活擴展和切換部件的具體構造和產品裝配的方式。
再直白點說,建造模式的重心在于分離構建算法和具體的構造實現,從而使得構建算法可以重用。具體的構造實現可以很方便地擴展和切換,從而可以靈活地組合來構造出不同的產品對象
建造者模式的優點
首先,建造者模式的封裝性很好。使用建造者模式可以有效的封裝變化,在使用建造者模式的場景中,一般產品類和建造者類是比較穩定的,因此,將主要的業務邏輯封裝在導演類中對整體而言可以取得比較好的穩定性。
其次,建造者模式很容易進行擴展。如果有新的需求,通過實現一個新的建造者類就可以完成,基本上不用修改之前已經測試通過的代碼,因此也就不會對原有功能引入風險。
建造者模式與工廠模式的區別
我們可以看到,建造者模式與工廠模式是極為相似的,總體上,建造者模式僅僅只比工廠模式多了一個“導演類”的角色。在建造者模式的類圖中,假如把這個導演類看做是最終調用的客戶端,那么圖中剩余的部分就可以看作是一個簡單的工廠模式了。
與工廠模式相比,建造者模式一般用來創建更為復雜的對象,因為對象的創建過程更為復雜,因此將對象的創建過程獨立出來組成一個新的類——導演類。也就是說,工廠模式是將對象的全部創建過程封裝在工廠類中,由工廠類向客戶端提供最終的產品;而建造者模式中,建造者類一般只提供產品類中各個組件的建造,而將具體建造過程交付給導演類。由導演類負責將各個組件按照特定的規則組建為產品,然后將組建好的產品交付給客戶端。