前言
建造模式 是對象的創建模式。建造模式可以將一個產品的內部表象(internal representation)與產品的生產過程分割開來,從而可以使一個建造過程生成具有不同的內部表象的產品對象。
(一). 產品的內部表象
一個產品常有不同的組成成分作為產品的零件,這些零件有可能是對象,也有可能不是對象,他們通常又稱為產品的內部表象(internal representation)。
(二). 對象性質的建造
有些情況下,一個對象會有些重要的性質,在它們沒有正確賦值之前,對象不能作為一個完整的產品使用。比如:一個電子郵件有發件人地址、收件人地址、主題、內容、附件等部分,而在最基本的發件人地址得到賦值之前,這個電子郵件是不可以發送的。
有些情況下,一個對象的有些性質必須按照某個順序賦值才有意義。在某個性質沒有賦值之前,另一個性質則無法賦值。
這些情況使得性質本身的建造設計到複雜的業務邏輯。設置後,此對象相當於一個有待建造的產品,而對象的這些性質相當於產品的零件,建造產品的過程是建造零件的過程。
由於建造零件的過程很複雜,因此,這些零件的建造過程往往被外部化到另一個成為建造者的對象中,建造者對象返還給客戶端的是一個全部零件都建造完畢的產品對象。
建造模式利用一個導演者對象和 具體建造者對象一個個的建造出所有的零件,從而建造出完整的產品對象。建造者模式將產品的結構和產品的零件的建造過程對客戶端隱藏起來,把對建造過程進行指揮的責任和具體建造者零件的責任分割開來,達到責任劃分和封裝的目的。正文
建造模式的結構
在這個示意的系統裡,最終產品Product只有兩個零件,即part1和part2。相應的構造方法也有兩個,即buildPart1()和buildPart2()。
同時可以看出本模式涉及到四個角色,它們分別為:
抽象建造者(Builder):
給出一個抽象接口,以規範產品對象的各個組成成分的建造。模式中真正創建產品對象的是具體建造者ConcreteBuilder角色。
具體建造者類必須實現這個接口要求的兩種方法:
- 一種是產品具體零件建造方法:buildPart1()和buildPart2();
- 另一種是返回構造完成的產品的方法retrieveResult()。
一般來說,產品所包含的零件數目與建造方法的數目相符。換言之,有多少零件需要建造,就會有多少相應的建造方法。
具體建造者(ContreteBuilder):
擔任這個角色的是抽象建造者在具體業務場景的下的建造實現。這個角色要完成的任務包括:
- 實現抽象建造者Builder所聲明的接口,給出一步步完成創建產品實例的操作。
- 在建造過程完成後,提供產品的實例。
導演者(Director):
擔任這個角色的類調用具體建造者角色以創建產品對象。應當指出的是,導演者角色並沒有產品類的具體知識,真正擁有產品類的具體知識的是具體建造者角色。
產品(Product):
產品便是建造中的複雜對象,一般來說,一個系統中會有多於一個的產品類,而且這些產品類並不一定有共同的接口,而完全可以是不相關聯的。
建造模式的示例代碼
Product.java
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;
}
@Override
public String toString() {
return "Product [part1=" + part1 + ", part2=" + part2 + "]";
}
}
Builder.java
/**
* 抽象建造者角色
*
* 提供零件建造方法及返回結果方法
*/
public interface Builder {
void buildPart1();
void buildPart2();
Product retrieveResult();
}
ConcreteBuilder.java
/**
* 具體建造者角色
*/
public class ConcreteBuilder implements Builder {
private Product product = new Product();
/**
* 建造零件1
*/
@Override
public void buildPart1() {
product.setPart1("零件分類1,編號:10000");
}
/**
* 建造零件2
*/
@Override
public void buildPart2() {
product.setPart2("零件分類2,編號:20000");
}
/**
* 返回建造後成功的產品
* @return
*/
@Override
public Product retrieveResult() {
return product;
}
}
Director.java
/**
* 導演者角色
*/
public class Director {
/**
* 創建建造者對象
*/
private Builder builder;
/**
* 構造函數,給定建造者對象
* @param builder 建造者對象
*/
public Director(Builder builder) {
this.builder = builder;
}
/**
* 產品構造方法,在該方法內,調用產品零件建造方法。
*/
public Product construct(){
builder.buildPart1();
builder.buildPart2();
// 返回builder建造完成的產品對象
return builder.construct();
}
}
Client.java
public class Client {
public static void main(String[] args) {
//創建具體建造者對象
Builder builder = new ConcreteBuilder();
//創造導演者角色,給定建造者對象
Director director = new Director(builder);
//調用導演者角色,創建產品零件。並返回產品建造結果。
Product product = director.construct();
System.out.println(product);
}
}
上述代碼完成的具體步驟:
- 客戶端創建具體建造者對象;
- 將具體建造者對象交給導演者;
- 導演者 操作建造者對象建造產品零件;
- 當產品創建完成後,導演者將產品返回給客戶端。
建造者模式構建複雜對象
考慮這樣一個實際業務應用,要創建一個保險合同的對象,裡面很多屬性的值都有約束,要求創建出來的對象是滿足這些約束規則的。
約束規則如下:
保險合同通常情況下可以和個人簽訂,也可以和某個公司簽訂個,但是一份保險合同不能同時和個人和公司簽訂。這個對象裡有很多類似於這樣的約束,採用建造者模式來構建複雜的對象,通常會對建造者模式進行一定的簡化,因為目標明確,就是創建某個複雜對象,因此做適當的簡化會使得程序更簡介。具體實現思路如下:
- 由於是用Builder建造者模式來創建某個對象,因此就沒有必要再定義一個Builder接口,直接提供一個具體的建造類就可以了。
- 對於創建一個複雜的對象,可能會有很多種不同的選擇和步驟,乾脆去掉導演者Director,把導演者的功能和Client客戶端的功能合併起來,也就是說Client客戶端的功能就相當於導演者,它來指導建造者去構建需要的複雜對象。
於是,建造者(Builder)可以抽象到目標產品(Product)的內部,這樣最大的好處對外
屏蔽掉具體的建造實現,是示例代碼如下:InstranceContract.java
/**
* 保險合同編號
*/
public class InstranceContract {
/**
* 保險合同編號
*/
private String contractId;
/**
* 受保人名稱,此處因為有限制條件:要麼同個人簽訂,要麼同公司簽訂
* 也就是說,受保人名稱屬性同受保公司名稱屬性不能同時有值。
*/
private String personName;
/**
* 受保公司名稱
*/
private String companyName;
/**
* 開始時間
*/
private long beginDate;
/**
* 結束時間,需要大於開始時間
*/
private long endDate;
/**
* 其他數據
*/
private String otherData;
private InstranceContract(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+"】");
System.out.println(this);
}
@Override
public String toString() {
return "InstranceContract [contractId=" + contractId +
", personName=" + personName +
", companyName="+ companyName +
", beginDate=" + beginDate +
", endDate=" + endDate +
", otherData=" + otherData +
"]";
}
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;
}
public InstranceContract 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 && !signCompany) {
throw new IllegalArgumentException("一份保險合同不能沒有簽訂對象");
}
if (beginDate <= 0) {
throw new IllegalArgumentException("一份保險合同必須有生效的日期");
}
if (endDate <= 0) {
throw new IllegalArgumentException("一份保險合同必須有失效的日期");
}
if (endDate <= beginDate) {
throw new IllegalArgumentException("一份保險合同的失效日期必須要大於生效的日期");
}
return new InstranceContract(this);
}
}
}
客戶端(Client)、導演者(Director)合併到一個類上面,如下:
public class Client {
public static void main(String[] args) {
InstranceContract.ConcreteBuilder builder =
new InstranceContract.ConcreteBuilder("8888", 1233L, 2253L);
// 導演者進行組裝
InstranceContract contract =
builder.setPersonName("趙四").setOtherData("測試數據").build();
contract.someOperation();
}
}
總結
建造者模式主要適用於如下的業務場景:
- 內部結構複雜:
需要生成的產品對象有複雜的內部結構,每一個內部組件本身也可以是複雜對象,也可以僅僅是一個簡單的組成部分。
- 屬性順序和依賴:
需要生成的產品對象的屬性相互依賴。建造模式可以強制實行一種分步驟順序進行的建造過程。因此,如果產品對象的一個屬性必須在另外一個屬性賦值之後才可以被賦值,那麼,使用建造者模式是一個很好的設計思想。
- 屬性獲取過程複雜:
在對象創建過程中會使用到系統中的一些其他對象,這些對象在產品對象的創建過程中不易得到。
閱讀更多 程序猿的內心獨白 的文章