23種設計模式(三)建造者模式

1.定義html

將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。算法

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

產品的內部表象:ide

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

對象性質的建造:ui

  有些狀況下,一個對象會有一些重要的性質,在它們沒有恰當的值以前,對象不能做爲一個完整的產品使用。好比,一個電子郵件有發件人地址、收件人地址、主題、內容、附錄等部分,而在最起碼的收件人地址獲得賦值以前,這個電子郵件不能發送。this

  有些狀況下,一個對象的一些性質必須按照某個順序賦值纔有意義。在某個性質沒有賦值以前,另外一個性質則沒法賦值。這些狀況使得性質自己的建造涉及到複雜的商業邏輯。這時候,此對象至關於一個有待建造的產品,而對象的這些性質至關於產品的零件,建造產品的過程是建造零件的過程。因爲建造零件的過程很複雜,所以,這些零件的建造過程每每被「外部化」到另外一個稱作建造者的對象裏,建造者對象返還給客戶端的是一個所有零件都建造完畢的產品對象。設計

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

2.適用性
同時知足如下狀況的時候可使用Builder模式
    a. 當建立複雜對象的算法應該獨立於該對象的組成部分以及他們的裝配方式
    b. 當構造過程必須容許構造的對象有不一樣的表示htm

3.結構

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

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

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

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

4)產品(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());
    }
}

時序圖

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

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

4.使用場景

    假設有一個電子雜誌系統,按期地向用戶的電子郵件信箱發送電子雜誌。用戶能夠經過網頁訂閱電子雜誌,也能夠經過網頁結束訂閱。當客戶開始訂閱時,系統發送一個電子郵件表示歡迎,當客戶結束訂閱時,系統發送一個電子郵件表示歡送。本例子就是這個系統負責發送「歡迎」和「歡送」郵件的模塊。

在本例中,產品類就是發給某個客戶的「歡迎」和「歡送」郵件,以下圖所示。

雖然在這個例子裏面各個產品類均有一個共同的接口,但這僅僅是本例子特有的,並不表明建造模式的特色。建造模式能夠應用到具備徹底不一樣接口的產品類上。大多數狀況下是不知道最終構建出來的產品是什麼樣的,因此在標準的建造模式裏面,通常是不須要對產品定義抽象接口的,由於最終構造的產品千差萬別,給這些產品定義公共接口幾乎是沒有意義的。

系統類圖以下:

抽象類AutoMessage源代碼,send()操做僅僅是示意性的,並無給出任何發送電子郵件的代碼。(builder)

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("toAddress@126.com", "fromAddress@126.com");  
    }
}

兩個重點:

 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來構建產品對象所致使的問題。另外,這個構建器的功能就是爲了建立被構建的對象,徹底能夠不用單獨一個類。

5.使用場景

  1. 須要生成的產品對象有複雜的內部結構,每個內部成分自己能夠是對象,也能夠僅僅是一個對象(即產品對象)的一個組成部分。

  2. 須要生成的產品對象的屬性相互依賴。建造模式能夠強制實行一種分步驟進行的建造過程,所以,若是產品對象的一個屬性必須在另外一個屬性被賦值以後才能夠被賦值,使用建造模式是一個很好的設計思想。

  3. 在對象建立過程當中會使用到系統中的其餘一些對象,這些對象在產品對象的建立過程當中不易獲得。

更詳細的解釋以下:

http://www.cnblogs.com/happyhippy/archive/2010/09/01/1814287.html

相關文章
相關標籤/搜索