《JAVA與模式》之建造模式

來源:互聯網
上載者:User

標籤:wxs   dmr   ifd   rdl   chm   lvm   protect   dsv   sms   

在閻宏博士的《JAVA與模式》一書中開頭是這樣描述建造(Builder)模式的:

  建造模式是對象的建立模式。建造模式可以將一個產品的內部表象(internal representation)與產品的生產過程分割開來,從而可以使一個建造過程產生具有不同的內部表象的產品對象。

產品的內部表象

  一個產品常有不同的組成成分作為產品的零件,這些零件有可能是對象,也有可能不是對象,它們通常又叫做產品的內部表象(internal representation)。不同的產品可以有不同的內部表象,也就是不同的零件。使用建造模式可以使用戶端不需要知道所產生的產品有哪些零件,每個產品的對應零件彼此有何不同,是怎麼建造出來的,以及怎麼組成產品。

對象性質的建造

  有些情況下,一個對象會有一些重要的性質,在它們沒有恰當的值之前,對象不能作為一個完整的產品使用。比如,一個電子郵件有寄件者地址、收件者地址、主題、內容、附錄等部分,而在最起碼的收件者地址得到賦值之前,這個電子郵件不能發送。

  有些情況下,一個對象的一些性質必須按照某個順序賦值才有意義。在某個性質沒有賦值之前,另一個性質則無法賦值。這些情況使得性質本身的建造涉及到複雜的商業邏輯。這時候,此對象相當於一個有待建造的產品,而對象的這些性質相當於產品的零件,建造產品的過程是建造零件的過程。由於建造零件的過程很複雜,因此,這些零件的建造過程往往被“外部化”到另一個稱做建造者的對象裡,建造者對象返還給用戶端的是一個全部零件都建造完畢的產品對象。

  建造模式利用一個導演者對象和具體建造者對象一個個地建造出所有的零件,從而建造出完整的產品對象。建造者模式將產品的結構和產品的零件的建造過程對用戶端隱藏起來,把對建造過程進行指揮的責任和具體建造者零件的責任分割開來,達到責任劃分和封裝的目的。 

建造模式的結構

  

  在這個示意性的系統裡,最終產品Product只有兩個零件,即part1和part2。相應的建造方法也有兩個:buildPart1()和buildPart2()、同時可以看出本模式涉及到四個角色,它們分別是:

  抽象建造者(Builder)角色:給 出一個抽象介面,以規範產品對象的各個組成成分的建造。一般而言,此介面獨立於應用程式的商業邏輯。模式中直接建立產品對象的是具體建造者 (ConcreteBuilder)角色。具體建造者類必須實現這個介面所要求的兩種方法:一種是建造方法(buildPart1和 buildPart2),另一種是返還結構方法(retrieveResult)。一般來說,產品所包含的零件數目與建造方法的數目相符。換言之,有多少 零件,就有多少相應的建造方法。

  具體建造者(ConcreteBuilder)角色:擔任這個角色的是與應用程式緊密相關的一些類,它們在應用程式調用下建立產品的執行個體。這個角色要完成的任務包括:1.實現抽象建造者Builder所聲明的介面,給出一步一步地完成建立產品執行個體的操作。2.在建造過程完成後,提供產品的執行個體。

  導演者(Director)角色:擔任這個角色的類調用具體建造者角色以建立產品對象。應當指出的是,導演者角色並沒有產品類的具體知識,真正擁有產品類的具體知識的是具體建造者角色。

  產品(Product)角色:產品便是建造中的複雜物件。一般來說,一個系統中會有多於一個的產品類,而且這些產品類並不一定有共同的介面,而完全可以是不相關聯的。

  

  導演者角色是與用戶端打交道的角色。導演者將用戶端建立產品的請求劃分為對各個零件的建造請求,再將這些請求委派給具體建造者角色。具體建造者角色是做具體建造工作的,但是卻不為用戶端所知。

  一般來說,每有一個產品類,就有一個相應的具體建造者類。這些產品應當有一樣數目的零件,而每有一個零件就相應地在所有的建造者角色裡有一個建造方法。

原始碼

 產品類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;
}
}

 抽象建造者類Builder

public interface Builder {
public void buildPart1();
public void buildPart2();
public Product retrieveResult();
}

  具體建造者類ConcreteBuilder

public class ConcreteBuilder implements Builder {

private Product product = new Product();
/**
* 產品零件建造方法1
*/
@Override
public void buildPart1() {
//構建產品的第一個零件
    product.setPart1("編號:9527");
}
/**
* 產品零件建造方法2
*/
@Override
public void buildPart2() {
//構建產品的第二個零件
    product.setPart2("名稱:XXX");
}
/**
* 產品返還方法
*/
@Override
public Product retrieveResult() {
return product;
}

}

   導演者類Director

public class Director {
/**
* 持有當前需要使用的建造器對象
*/
private Builder builder;
/**
* 構造方法,傳入建造器對象
* @param builder 建造器對象
*/
public Director(Builder builder){
this.builder = builder;
}
/**
* 產品構造方法,負責調用各個零件建造方法
*/
public void construct(){
builder.buildPart1();
builder.buildPart2();
}
}

  用戶端類Client

public class Client {
public static void main(String[]args){
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product = builder.retrieveResult();
System.out.println(product.getPart1());
System.out.println(product.getPart2());
}
}
時序圖

  用戶端負責建立導演者和具體建造者對象。然後,用戶端把具體建造者對象交給導演者,導演者操作具體建造者,開始建立產品。當產品完成後,建造者把產品返還給用戶端。

  把建立具體建造者對象的任務交給用戶端而不是導演者對象,是為了將導演者對象與具體建造者對象的耦合變成動態,從而使導演者對象可以操縱數個具體建造者對象中的任何一個。

 

使用情境

  假設有一個電子雜誌系統,定期地向使用者的電子郵件信箱發送電子雜誌。使用者可以通過網頁訂閱電子雜誌,也可以通過網頁結束訂閱。當客戶開始訂閱時,系統發送一個電子郵件表示歡迎,當客戶結束訂閱時,系統發送一個電子郵件表示歡送。本例子就是這個系統負責發送“歡迎”和“歡送”郵件的模組。

  在本例中,產品類就是發給某個客戶的“歡迎”和“歡送”郵件,如所示。

  雖然在這個例子裡面各個產品類均有一個共同的介面,但這僅僅是本例子特有的,並不代表建造模式的特點。建造模式可以應用到具有完全不同介面的產品類上。大多數情況下是不知道最終構建出來的產品是什麼樣的,所以在標準的建造模式裡面,一般是不需要對產品定義抽象介面的,因為最終構造的產品千差萬別,給這些產品定義公用介面幾乎是沒有意義的。

  所示就是這個系統的類圖。

  

  這個系統含有用戶端(Client)、導演者(Director)、抽象建造者(Builder)、具體建造者(WelcomeBuilder和GoodbyeBuilder)、產品(WelcomeMessage和GoodbyeMessage)等角色。

原始碼

  抽象類別AutoMessage原始碼,send()操作僅僅是示意性的,並沒有給出任何寄送電子郵件的代碼。

public abstract class AutoMessage {
//收件者地址
private String to;
//寄件者地址
private String from;
//標題
private String subject;
//內容
private String body;
//發送日期
private Date sendDate;
public void send(){
System.out.println("收件者地址:" + to);
System.out.println("寄件者地址:" + from);
System.out.println("標題:" + subject);
System.out.println("內容:" + body);
System.out.println("發送日期:" + sendDate);
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Date getSendDate() {
return sendDate;
}
public void setSendDate(Date sendDate) {
this.sendDate = sendDate;
}

}

  具體產品類WelcomeMessage

public class WelcomeMessage extends AutoMessage {
/**
* 構造子
*/
public WelcomeMessage(){
System.out.println("發送歡迎資訊");
}
}

  具體產品類GoodbyeMessage

public class GoodbyeMessage extends AutoMessage{
/**
* 構造子
*/
public GoodbyeMessage(){
System.out.println("發送歡送資訊");
}
}

  抽象建造者類

public abstract class Builder {
protected AutoMessage msg;
//標題零件的建造方法
public abstract void buildSubject();
//內容零件的建造方法
public abstract void buildBody();
//收件者零件的建造方法
public void buildTo(String to){
msg.setTo(to);
}
//寄件者零件的建造方法
public void buildFrom(String from){
msg.setFrom(from);
}
//發送時間零件的建造方法
public void buildSendDate(){
msg.setSendDate(new Date());
}
/**
* 郵件產品完成後,用此方法發送郵件
* 此方法相當於產品返還方法
*/
public void sendMessage(){
msg.send();
}
}

  具體建造者WelcomeBuilder

public class WelcomeBuilder extends Builder {
public WelcomeBuilder(){
msg = new WelcomeMessage();
}
@Override
public void buildBody() {
// TODO Auto-generated method stub
    msg.setBody("歡迎內容");
}

@Override
public void buildSubject() {
// TODO Auto-generated method stub
    msg.setSubject("歡迎標題");
}

}

  具體建造者GoodbyeBuilder

public class GoodbyeBuilder extends Builder {

public GoodbyeBuilder(){
msg = new GoodbyeMessage();
}
@Override
public void buildBody() {
// TODO Auto-generated method stub
    msg.setBody("歡送內容");
}

@Override
public void buildSubject() {
// TODO Auto-generated method stub
    msg.setSubject("歡送標題");
}

}

  導演者Director,這個類提供一個construct()方法,此方法調用建造者的建造方法,包括buildTo()、buildFrom()、buildSubject()、buildBody()、buildSendDate()等,從而一部分一部分地建造出產品對象,既AutoMessage對象。

public class Director {
Builder builder;
/**
* 構造子
*/
public Director(Builder builder){
this.builder = builder;
}
/**
* 產品構造方法,負責調用各零件的建造方法
*/
public void construct(String toAddress , String fromAddress){
this.builder.buildTo(toAddress);
this.builder.buildFrom(fromAddress);
this.builder.buildSubject();
this.builder.buildBody();
this.builder.buildSendDate();
this.builder.sendMessage();
}
}

  用戶端Client

public class Client {

public static void main(String[] args) {
// TODO Auto-generated method stub
    Builder builder = new WelcomeBuilder();
Director director = new Director(builder);
director.construct("[email protected]", "[email protected]");

}

}

  建造模式分成兩個很重要的部分:

  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. 在對象建立過程中會使用到系統中的其他一些對象,這些對象在產品對象的建立過程中不易得到。

《JAVA與模式》之建造模式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.