【創(chuàng)建型模式五】生成器(Builder)

1 場景問題#

1.1 繼續(xù)導(dǎo)出數(shù)據(jù)的應(yīng)用框架##

在討論工廠方法模式的時候,提到了一個導(dǎo)出數(shù)據(jù)的應(yīng)用框架。

對于導(dǎo)出數(shù)據(jù)的應(yīng)用框架,通常在導(dǎo)出數(shù)據(jù)上,會有一些約定的方式,比如導(dǎo)出成:文本格式、數(shù)據(jù)庫備份形式、Excel格式、Xml格式等等。

在工廠方法模式章節(jié)里面,討論并使用工廠方法模式來解決了如何選擇具體導(dǎo)出方式的問題,并沒有涉及到每種方式具體如何實現(xiàn)。換句話說,在討論工廠方法模式的時候,并沒有討論如何實現(xiàn)導(dǎo)出成文本、Xml等具體的格式,本章就來討論這個問題。

對于導(dǎo)出數(shù)據(jù)的應(yīng)用框架,通常對于具體的導(dǎo)出內(nèi)容和格式是有要求的,假如現(xiàn)在有如下的要求,簡單描述一下:

導(dǎo)出的文件,不管什么格式,都分成三個部分,分別是文件頭、文件體和文件尾

在文件頭部分,需要描述如下信息:分公司或門市點編號、導(dǎo)出數(shù)據(jù)的日期,對于文本格式,中間用逗號分隔

在文件體部分,需要描述如下信息:表名稱、然后分條描述數(shù)據(jù)。對于文本格式,表名稱單獨占一行,數(shù)據(jù)描述一行算一條數(shù)據(jù),字段間用逗號分隔。

在文件尾部分,需要描述如下信息:輸出人

現(xiàn)在就要來實現(xiàn)上述功能。為了演示簡單點,在工廠方法模式里面已經(jīng)實現(xiàn)的功能,這里就不去重復(fù)了,這里只關(guān)心如何實現(xiàn)導(dǎo)出文件,而且只實現(xiàn)導(dǎo)出成文本格式和XML格式就可以了,其它的就不去考慮了。

1.2 不用模式的解決方案##

不就是要實現(xiàn)導(dǎo)出數(shù)據(jù)到文本文件和XML文件嗎,其實不管什么格式,需要導(dǎo)出的數(shù)據(jù)是一樣的,只是具體導(dǎo)出到文件中的內(nèi)容,會隨著格式的不同而不同。

  1. 先來把描述文件各個部分的數(shù)據(jù)對象定義出來,先看描述輸出到文件頭的內(nèi)容的對象,示例代碼如下:
/**
   * 描述輸出到文件頭的內(nèi)容的對象
   */
public class ExportHeaderModel {
    /**
     * 分公司或門市點編號
     */
    private String depId;
    /**
     * 導(dǎo)出數(shù)據(jù)的日期
     */
    private String exportDate;
    public String getDepId() {
       return depId;
    }
    public void setDepId(String depId) {
       this.depId = depId;
    }
    public String getExportDate() {
       return exportDate;
    }
    public void setExportDate(String exportDate) {
       this.exportDate = exportDate;
    }
}

接下來看看描述輸出數(shù)據(jù)的對象,示例代碼如下:

/**
   * 描述輸出數(shù)據(jù)的對象
   */
public class ExportDataModel {
    /**
     * 產(chǎn)品編號
     */
    private String productId;
    /**
     * 銷售價格
     */
    private double price;
    /**
     * 銷售數(shù)量
     */
    private double amount;

    public String getProductId() {
       return productId;
    }
    public void setProductId(String productId) {
       this.productId = productId;
    }
    public double getPrice() {
       return price;
    }
    public void setPrice(double price) {
       this.price = price;
    }
    public double getAmount() {
       return amount;
    }
    public void setAmount(double amount) {
       this.amount = amount;
    }
}

接下來看看描述輸出到文件尾的內(nèi)容的對象,示例代碼如下:

/**
   * 描述輸出到文件尾的內(nèi)容的對象
   */
public class ExportFooterModel {
    /**
     * 輸出人
     */
    private String exportUser;
    public String getExportUser() {
       return exportUser;
    }
    public void setExportUser(String exportUser) {
       this.exportUser = exportUser;
    }
}
  1. 接下來具體的看看導(dǎo)出的實現(xiàn),先看導(dǎo)出數(shù)據(jù)到文本文件的對象,主要就是要實現(xiàn)拼接輸出的內(nèi)容,示例代碼如下:
/**
   * 導(dǎo)出數(shù)據(jù)到文本文件的對象
   */
public class ExportToTxt {
    /**
     * 導(dǎo)出數(shù)據(jù)到文本文件
     * @param ehm 文件頭的內(nèi)容
     * @param mapData 數(shù)據(jù)的內(nèi)容
     * @param efm 文件尾的內(nèi)容
     */
    public void export(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm){
       //用來記錄最終輸出的文件內(nèi)容
       StringBuffer buffer = new StringBuffer();
       //1:先來拼接文件頭的內(nèi)容
       buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n");
       //2:接著來拼接文件體的內(nèi)容
       for(String tblName : mapData.keySet()){
           //先拼接表名稱
           buffer.append(tblName+"\n");
           //然后循環(huán)拼接具體數(shù)據(jù)
           for(ExportDataModel edm : mapData.get(tblName)){
              buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n");
           }
       }
       //3:接著來拼接文件尾的內(nèi)容
       buffer.append(efm.getExportUser());

       //為了演示簡潔性,這里就不去寫輸出文件的代碼了
       //把要輸出的內(nèi)容輸出到控制臺看看
       System.out.println("輸出到文本文件的內(nèi)容:\n"+buffer);
    }
}
  1. 接下來看看導(dǎo)出數(shù)據(jù)到XML文件的對象,比較麻煩,要按照XML的格式進(jìn)行拼接,示例代碼如下:
/**
   * 導(dǎo)出數(shù)據(jù)到XML文件的對象
   */
public class ExportToXml {
    /**
     * 導(dǎo)出數(shù)據(jù)到XML文件
     * @param ehm 文件頭的內(nèi)容
     * @param mapData 數(shù)據(jù)的內(nèi)容
     * @param efm 文件尾的內(nèi)容
     */
    public void export(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm){
        //用來記錄最終輸出的文件內(nèi)容
        StringBuffer buffer = new StringBuffer();
        //1:先來拼接文件頭的內(nèi)容
        buffer.append("<?xml version='1.0' encoding='gb2312'?>\n");
        buffer.append("<Report>\n");
        buffer.append("  <Header>\n");
        buffer.append("    <DepId>"+ehm.getDepId()+"</DepId>\n");
        buffer.append("    <ExportDate>"+ehm.getExportDate()+"</ExportDate>\n");
        buffer.append("  </Header>\n");
        //2:接著來拼接文件體的內(nèi)容
        buffer.append("  <Body>\n");
        for(String tblName : mapData.keySet()){
            //先拼接表名稱
            buffer.append("    <Datas TableName=\""+tblName+"\">\n");
            //然后循環(huán)拼接具體數(shù)據(jù)
            for(ExportDataModel edm : mapData.get(tblName)){
                buffer.append("      <Data>\n");
                buffer.append("          <ProductId>"+edm.getProductId()+"</ProductId>\n");
                buffer.append("          <Price>"+edm.getPrice()+"</Price>\n");
                buffer.append("          <Amount>"+edm.getAmount()+"</Amount>\n");
                buffer.append("      </Data>\n");
            }
            buffer.append("    </Datas>\n");
        }
        buffer.append("  </Body>\n");
        //3:接著來拼接文件尾的內(nèi)容
        buffer.append("  <Footer>\n");
        buffer.append("    <ExportUser>"+efm.getExportUser()+"</ExportUser>\n");
        buffer.append("  </Footer>\n");
        buffer.append("</Report>\n");

        //為了演示簡潔性,這里就不去寫輸出文件的代碼了
        //把要輸出的內(nèi)容輸出到控制臺看看
        System.out.println("輸出到XML文件的內(nèi)容:\n"+buffer);
    }
}
  1. 看看客戶端,如何來使用這些對象,示例代碼如下:
public class Client {
    public static void main(String[] args) {
       //準(zhǔn)備測試數(shù)據(jù)
       ExportHeaderModel ehm = new ExportHeaderModel();
       ehm.setDepId("一分公司");
       ehm.setExportDate("2010-05-18");

       Map<String,Collection<ExportDataModel>> mapData = new HashMap<String,Collection<ExportDataModel>>();
       Collection<ExportDataModel> col = new ArrayList<ExportDataModel>();

       ExportDataModel edm1 = new ExportDataModel();
       edm1.setProductId("產(chǎn)品001號");
       edm1.setPrice(100);
       edm1.setAmount(80);

       ExportDataModel edm2 = new ExportDataModel();
       edm2.setProductId("產(chǎn)品002號");
       edm2.setPrice(99);
       edm2.setAmount(55);     
       //把數(shù)據(jù)組裝起來
       col.add(edm1);
       col.add(edm2);      
       mapData.put("銷售記錄表", col);

       ExportFooterModel efm = new ExportFooterModel();
       efm.setExportUser("張三");     
       //測試輸出到文本文件
       ExportToTxt toTxt = new ExportToTxt();
       toTxt.export(ehm, mapData, efm);
       //測試輸出到xml文件
       ExportToXml toXml = new ExportToXml();
       toXml.export(ehm, mapData, efm);
    }
}

運行結(jié)果如下:

運行結(jié)果

1.3 有何問題##

仔細(xì)觀察上面的實現(xiàn),會發(fā)現(xiàn),不管是輸出成文本文件,還是輸出到XML文件,在實現(xiàn)的時候,步驟基本上都是一樣的,都大致分成了如下四步:

  1. 先拼接文件頭的內(nèi)容;
  2. 然后拼接文件體的內(nèi)容;
  3. 再拼接文件尾的內(nèi)容;
  4. 最后把拼接好的內(nèi)容輸出出去成為文件;

也就是說,對于不同的輸出格式,處理步驟是一樣的,但是具體每步的實現(xiàn)是不一樣的。按照現(xiàn)在的實現(xiàn)方式,就存在如下的問題:

  1. 構(gòu)建每種輸出格式的文件內(nèi)容的時候,都會重復(fù)這幾個處理步驟,應(yīng)該提煉出來,形成公共的處理過程

  2. 今后可能會有很多不同輸出格式的要求,這就需要在處理過程不變的情況下,能方便的切換不同的輸出格式的處理

換句話來說,也就是構(gòu)建每種格式的數(shù)據(jù)文件的處理過程,應(yīng)該和具體的步驟實現(xiàn)分開,這樣就能夠復(fù)用處理過程,而且能很容易的切換不同的輸出格式

可是該如何實現(xiàn)呢?

2 解決方案#

2.1 生成器模式來解決##

用來解決上述問題的一個合理的解決方案就是生成器模式。那么什么是生成器模式呢?

  1. 生成器模式定義
生成器模式定義
  1. 應(yīng)用生成器模式來解決的思路

仔細(xì)分析上面的實現(xiàn),構(gòu)建每種格式的數(shù)據(jù)文件的處理過程,這不就是構(gòu)建過程嗎?而每種格式具體的步驟實現(xiàn),不就相當(dāng)于是不同的表示嗎?因為不同的步驟實現(xiàn),決定了最終的表現(xiàn)也就不同。也就是說,上面的問題恰好就是生成器模式要解決的問題。

要實現(xiàn)同樣的構(gòu)建過程可以創(chuàng)建不同的表現(xiàn),那么一個自然的思路就是先把構(gòu)建過程獨立出來,在生成器模式中把它稱為指導(dǎo)者,由它來指導(dǎo)裝配過程,但是不負(fù)責(zé)每步具體的實現(xiàn)。當(dāng)然,光有指導(dǎo)者是不夠的,必須要有能具體實現(xiàn)每步的對象,在生成器模式中稱這些實現(xiàn)對象為生成器

這樣一來,指導(dǎo)者就是可以重用的構(gòu)建過程,而生成器是可以被切換的具體實現(xiàn)。前面的實現(xiàn)中,每種具體的導(dǎo)出文件格式的實現(xiàn)就相當(dāng)于生成器。

2.2 模式結(jié)構(gòu)和說明##

生成器模式的結(jié)構(gòu)如圖所示:

生成器模式的結(jié)構(gòu)

Builder:生成器接口,定義創(chuàng)建一個Product對象所需的各個部件的操作。

ConcreteBuilder:具體的生成器實現(xiàn),實現(xiàn)各個部件的創(chuàng)建,并負(fù)責(zé)組裝Product對象的各個部件,同時還提供一個讓用戶獲取組裝完成后的產(chǎn)品對象的方法。

Director:指導(dǎo)者,也被稱為導(dǎo)向者,主要用來使用Builder接口,以一個統(tǒng)一的過程來構(gòu)建所需要的Product對象。

Product:產(chǎn)品,表示被生成器構(gòu)建的復(fù)雜對象,包含多個部件。

2.3 生成器模式示例代碼##

  1. 先看看生成器的接口定義,示例代碼如下:
/**
   * 生成器接口,定義創(chuàng)建一個產(chǎn)品對象所需的各個部件的操作
   */
public interface Builder {
    /**
     * 示意方法,構(gòu)建某個部件
     */
    public void buildPart();
}
  1. 再看看具體的生成器實現(xiàn),示例代碼如下:
/**
   * 具體的生成器實現(xiàn)對象
   */
public class ConcreteBuilder implements Builder {
    /**
     * 生成器最終構(gòu)建的產(chǎn)品對象
     */
    private Product resultProduct;
    /**
     * 獲取生成器最終構(gòu)建的產(chǎn)品對象
     * @return 生成器最終構(gòu)建的產(chǎn)品對象
     */
    public Product getResult() {
       return resultProduct;
    }

    public void buildPart() {
       //構(gòu)建某個部件的功能處理
    }
}
  1. 看看相應(yīng)的產(chǎn)品對象的接口示意,示例代碼如下:
/**
   * 被構(gòu)建的產(chǎn)品對象的接口
   */
public interface Product {
    //定義產(chǎn)品的操作
}
  1. 再來看看指導(dǎo)者的實現(xiàn)示意,示例代碼如下:
/**
   * 指導(dǎo)者,指導(dǎo)使用生成器的接口來構(gòu)建產(chǎn)品的對象
   */
public class Director {
    /**
     * 持有當(dāng)前需要使用的生成器對象
     */
    private Builder builder;

    /**
     * 構(gòu)造方法,傳入生成器對象
     * @param builder 生成器對象
     */
    public Director(Builder builder) {
       this.builder = builder;
    }

    /**
     * 示意方法,指導(dǎo)生成器構(gòu)建最終的產(chǎn)品對象
     */
    public void construct() {
       //通過使用生成器接口來構(gòu)建最終的產(chǎn)品對象
       builder.buildPart();
    }
}

2.4 使用生成器模式重寫示例##

要使用生成器模式來重寫示例,重要的任務(wù)就是要把指導(dǎo)者和生成器接口定義出來。指導(dǎo)者就是用來執(zhí)行那四個步驟的對象,而生成器是用來實現(xiàn)每種格式下,對于每個步驟的具體實現(xiàn)的對象

按照生成器模式重寫示例的結(jié)構(gòu)如圖所示:

按照生成器模式重寫示例的結(jié)構(gòu)
  1. 前面示例中的三個數(shù)據(jù)模型對象還繼續(xù)沿用,這里就不去贅述了。

  2. 先來看看定義的Builder接口,主要是把導(dǎo)出各種格式文件的處理過程的步驟定義出來,每個步驟負(fù)責(zé)構(gòu)建最終導(dǎo)出文件的一部分。示例代碼如下:

/**
   * 生成器接口,定義創(chuàng)建一個輸出文件對象所需的各個部件的操作
   */
public interface Builder {
    /**
     * 構(gòu)建輸出文件的Header部分
     * @param ehm 文件頭的內(nèi)容
     */
    public void buildHeader(ExportHeaderModel ehm);
    /**
     * 構(gòu)建輸出文件的Body部分
     * @param mapData 要輸出的數(shù)據(jù)的內(nèi)容
     */
    public void buildBody(Map<String,Collection<ExportDataModel>> mapData);
    /**
     * 構(gòu)建輸出文件的Footer部分
     * @param efm 文件尾的內(nèi)容
     */
    public void buildFooter(ExportFooterModel efm);
}
  1. 接下來看看具體的生成器實現(xiàn),其實就是把原來示例中,寫在一起的實現(xiàn),分拆成多個步驟實現(xiàn)了,先看看導(dǎo)出數(shù)據(jù)到文本文件的生成器實現(xiàn),示例代碼如下:
/**
   * 實現(xiàn)導(dǎo)出數(shù)據(jù)到文本文件的的生成器對象
   */
public class TxtBuilder implements Builder {
    /**
     * 用來記錄構(gòu)建的文件的內(nèi)容,相當(dāng)于產(chǎn)品
     */
    private StringBuffer buffer = new StringBuffer();

    public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {
       for(String tblName : mapData.keySet()){
           //先拼接表名稱
           buffer.append(tblName+"\n");
           //然后循環(huán)拼接具體數(shù)據(jù)
           for(ExportDataModel edm : mapData.get(tblName)){
              buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n");
           }
       }
    }
    public void buildFooter(ExportFooterModel efm) {
       buffer.append(efm.getExportUser());
    }
    public void buildHeader(ExportHeaderModel ehm) {
       buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n");
    }  
    public StringBuffer getResult(){
       return buffer;
    }   
}

再看看導(dǎo)出數(shù)據(jù)到XML文件的生成器實現(xiàn),示例代碼如下:

/**
   * 實現(xiàn)導(dǎo)出數(shù)據(jù)到XML文件的的生成器對象
   */
public class XmlBuilder implements Builder {
    /**
     * 用來記錄構(gòu)建的文件的內(nèi)容,相當(dāng)于產(chǎn)品
     */
    private StringBuffer buffer = new StringBuffer();

    public void buildBody(Map<String, Collection<ExportDataModel>> mapData){
       buffer.append("  <Body>\n");
       for(String tblName : mapData.keySet()){
           //先拼接表名稱
           buffer.append("    <Datas TableName=\""+tblName+"\">\n");
           //然后循環(huán)拼接具體數(shù)據(jù)
           for(ExportDataModel edm : mapData.get(tblName)){
              buffer.append("      <Data>\n");
              buffer.append("        <ProductId>"+edm.getProductId()+"</ProductId>\n");
              buffer.append("        <Price>"+edm.getPrice()+"</Price>\n");
              buffer.append("        <Amount>"+edm.getAmount()+"</Amount>\n");
              buffer.append("      </Data>\n");
           }
           buffer.append("    </Datas>\n");
       }
       buffer.append("  </Body>\n");
    }
    public void buildFooter(ExportFooterModel efm) {
       buffer.append("  <Footer>\n");
       buffer.append("    <ExportUser>"+efm.getExportUser()+"</ExportUser>\n");
       buffer.append("  </Footer>\n");
       buffer.append("</Report>\n");
    }
    public void buildHeader(ExportHeaderModel ehm) {
       buffer.append("<?xml version='1.0' encoding='gb2312'?>\n");
       buffer.append("<Report>\n");
       buffer.append("  <Header>\n");
       buffer.append("    <DepId>"+ehm.getDepId()+"</DepId>\n");
       buffer.append("    <ExportDate>"+ehm.getExportDate()+"</ExportDate>\n");
       buffer.append("  </Header>\n");
    }
    public StringBuffer getResult(){
       return buffer;
    }
}
  1. 指導(dǎo)者

有了具體的生成器實現(xiàn)后,需要有指導(dǎo)者來指導(dǎo)它進(jìn)行具體的產(chǎn)品構(gòu)建,由于構(gòu)建的產(chǎn)品是文本內(nèi)容,所以就不用單獨定義產(chǎn)品對象了。示例代碼如下:

/**
   * 指導(dǎo)者,指導(dǎo)使用生成器的接口來構(gòu)建輸出的文件的對象
   */
public class Director {
    /**
     * 持有當(dāng)前需要使用的生成器對象
     */
    private Builder builder;
    /**
     * 構(gòu)造方法,傳入生成器對象
     * @param builder 生成器對象
     */
    public Director(Builder builder) {
       this.builder = builder;
    }

    /**
     * 指導(dǎo)生成器構(gòu)建最終的輸出的文件的對象
     * @param ehm 文件頭的內(nèi)容
     * @param mapData 數(shù)據(jù)的內(nèi)容
     * @param efm 文件尾的內(nèi)容
     */
    public void construct(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm) {
       //1:先構(gòu)建Header
       builder.buildHeader(ehm);
       //2:然后構(gòu)建Body
       builder.buildBody(mapData);
       //3:然后構(gòu)建Footer
       builder.buildFooter(efm);
    }
}
  1. 都實現(xiàn)得差不多了,該來寫個客戶端好好測試一下了。示例代碼如下:
public class Client {
    public static void main(String[] args) {
       //準(zhǔn)備測試數(shù)據(jù)
       ExportHeaderModel ehm = new ExportHeaderModel();
       ehm.setDepId("一分公司");
       ehm.setExportDate("2010-05-18");

       Map<String,Collection<ExportDataModel>> mapData = new HashMap<String,Collection<ExportDataModel>>();
       Collection<ExportDataModel> col = new ArrayList<ExportDataModel>();

       ExportDataModel edm1 = new ExportDataModel();
       edm1.setProductId("產(chǎn)品001號");
       edm1.setPrice(100);
       edm1.setAmount(80);

       ExportDataModel edm2 = new ExportDataModel();
       edm2.setProductId("產(chǎn)品002號");
       edm2.setPrice(99);
       edm2.setAmount(55);     
       //把數(shù)據(jù)組裝起來
       col.add(edm1);
       col.add(edm2);      
       mapData.put("銷售記錄表", col);

       ExportFooterModel efm = new ExportFooterModel();
       efm.setExportUser("張三");

       //測試輸出到文本文件
       TxtBuilder txtBuilder = new TxtBuilder();
       //創(chuàng)建指導(dǎo)者對象
       Director director = new Director(txtBuilder);
       director.construct(ehm, mapData, efm);
       //把要輸出的內(nèi)容輸出到控制臺看看
       System.out.println("輸出到文本文件的內(nèi)容:\n"+txtBuilder.getResult());
       //測試輸出到xml文件
       XmlBuilder xmlBuilder = new XmlBuilder();
       Director director2 = new Director(xmlBuilder);
       director2.construct(ehm, mapData, efm);
       //把要輸出的內(nèi)容輸出到控制臺看看
       System.out.println("輸出到XML文件的內(nèi)容:\n"+xmlBuilder.getResult());
    }
}

看了上面的示例會發(fā)現(xiàn),其實生成器模式也挺簡單的,好好理解一下。通過上面的講述,應(yīng)該能很清晰的看出生成器模式的實現(xiàn)方式和它的優(yōu)勢所在了,那就是對同一個構(gòu)建過程,只要配置不同的生成器實現(xiàn),就會生成出不同表現(xiàn)的對象

3 模式講解#

3.1 認(rèn)識生成器模式##

  1. 生成器模式的功能

生成器模式的主要功能是構(gòu)建復(fù)雜的產(chǎn)品,而且是細(xì)化的,分步驟的構(gòu)建產(chǎn)品,也就是生成器模式重在解決一步一步構(gòu)造復(fù)雜對象的問題。如果光是這么認(rèn)識生成器模式的功能是不夠的。

更為重要的是,這個構(gòu)建的過程是統(tǒng)一的,固定不變的,變化的部分放到生成器部分了,只要配置不同的生成器,那么同樣的構(gòu)建過程,就能構(gòu)建出不同的產(chǎn)品表示來

再直白點說,生成器模式的重心在于分離構(gòu)建算法和具體的構(gòu)造實現(xiàn),從而使得構(gòu)建算法可以重用,具體的構(gòu)造實現(xiàn)可以很方便的擴展和切換,從而可以靈活的組合來構(gòu)造出不同的產(chǎn)品對象。

  1. 生成器模式的構(gòu)成

要特別注意,生成器模式分成兩個很重要的部分:

一個部分是Builder接口這邊,這邊是定義了如何構(gòu)建各個部件,也就是知道每個部件功能如何實現(xiàn),以及如何裝配這些部件到產(chǎn)品中去。

另外一個部分是Director這邊,Director是知道如何組合來構(gòu)建產(chǎn)品,也就是說Director負(fù)責(zé)整體的構(gòu)建算法,而且通常是分步驟的來執(zhí)行。

不管如何變化,Builder模式都存在這么兩個部分,一個部分是部件構(gòu)造和產(chǎn)品裝配,另一個部分是整體構(gòu)建的算法。認(rèn)識這點是很重要的,因為在生成器模式中,強調(diào)的是固定整體構(gòu)建的算法,而靈活擴展和切換部件的具體構(gòu)造和產(chǎn)品裝配的方式,所以要嚴(yán)格區(qū)分這兩個部分。

在Director實現(xiàn)整體構(gòu)建算法的時候,遇到需要創(chuàng)建和組合具體部件的時候,就會把這些功能通過委托,交給Builder去完成。

  1. 生成器模式的使用

應(yīng)用生成器模式的時候,可以讓客戶端創(chuàng)造Director,在Director里面封裝整體構(gòu)建算法,然后讓Director去調(diào)用Builder,讓Builder來封裝具體部件的構(gòu)建功能,這就跟前面的例子一樣。

還有一種退化的情況,就是讓客戶端和Director融合起來,讓客戶端直接去操作Builder,就好像是指導(dǎo)者自己想要給自己構(gòu)建產(chǎn)品一樣

  1. 生成器模式的調(diào)用順序示意圖
生成器模式的調(diào)用順序示意圖

3.2 生成器模式的實現(xiàn)##

  1. 生成器的實現(xiàn)

實際上在Builder接口的實現(xiàn)中,每個部件構(gòu)建的方法里面,除了部件裝配外,也可以實現(xiàn)如何具體的創(chuàng)建各個部件對象,也就是說每個方法都可以有兩部分功能,一個是創(chuàng)建部件對象,一個是組裝部件

在構(gòu)建部件的方法里面可以實現(xiàn)選擇并創(chuàng)建具體的部件對象,然后再把這個部件對象組裝到產(chǎn)品對象中去,這樣一來,Builder就可以和工廠方法配合使用了

再進(jìn)一步,如果在實現(xiàn)Builder的時候,只有創(chuàng)建對象的功能,而沒有組裝的功能,那么這個時候的Builder實現(xiàn)跟抽象工廠的實現(xiàn)是類似的

這種情況下,Builder接口就類似于抽象工廠的接口,Builder的具體實現(xiàn)就類似于具體的工廠,而且Builder接口里面定義的創(chuàng)建各個部件的方法也是有關(guān)聯(lián)的,這些方法是構(gòu)建一個復(fù)雜對象所需要的部件對象,仔細(xì)想想,是不是非常類似呢。

  1. 指導(dǎo)者的實現(xiàn)

在生成器模式里面,指導(dǎo)者承擔(dān)的是整體構(gòu)建算法部分,是相對不變的部分。因此在實現(xiàn)指導(dǎo)者的時候,把變化的部分分離出去是很重要的。

其實指導(dǎo)者分離出去的變化部分,就到了生成器那邊,指導(dǎo)者知道整體的構(gòu)建算法,就是不知道如何具體的創(chuàng)建和裝配部件對象

因此真正的指導(dǎo)者實現(xiàn),并不僅僅是如同前面示例那樣,簡單的按照一定順序調(diào)用生成器的方法來生成對象,并沒有這么簡單。應(yīng)該是有較為復(fù)雜的算法和運算過程,在運算過程中根據(jù)需要,才會調(diào)用生成器的方法來生成部件對象

  1. 指導(dǎo)者和生成器的交互

在生成器模式里面,指導(dǎo)者和生成器的交互,是通過生成器的那些buildPart方法來完成的。在前面的示例中,指導(dǎo)者和生成器是沒有太多相互交互的,指導(dǎo)者僅僅只是簡單的調(diào)用了一下生成器的方法,在實際開發(fā)中,這是遠(yuǎn)遠(yuǎn)不夠的。

指導(dǎo)者通常會實現(xiàn)比較復(fù)雜的算法或者是運算過程,在實際中很可能會有這樣的情況:

在運行指導(dǎo)者的時候,會按照整體構(gòu)建算法的步驟進(jìn)行運算,可能先運行前幾步運算,到了某一步驟,需要具體創(chuàng)建某個部件對象了,然后就調(diào)用Builder中創(chuàng)建相應(yīng)部件的方法來創(chuàng)建具體的部件。同時,把前面運算得到的數(shù)據(jù)傳遞給Builder,因為在Builder內(nèi)部實現(xiàn)創(chuàng)建和組裝部件的時候,可能會需要這些數(shù)據(jù);

Builder創(chuàng)建完具體的部件對象后,會把創(chuàng)建好的部件對象返回給指導(dǎo)者,指導(dǎo)者繼續(xù)后續(xù)的算法運算,可能會用到已經(jīng)創(chuàng)建好的對象

如此反復(fù)下去,直到整個構(gòu)建算法運行完成,那么最終的產(chǎn)品對象也就創(chuàng)建好了;

通過上面的描述,可以看出指導(dǎo)者和生成器是需要交互的,方式就是通過生成器方法的參數(shù)和返回值,來回的傳遞數(shù)據(jù)。事實上,指導(dǎo)者是通過委托的方式來把功能交給生成器去完成

  1. 返回裝配好的產(chǎn)品的方法

在標(biāo)準(zhǔn)的生成器模式里面,在Builder實現(xiàn)里面會提供一個返回裝配好的產(chǎn)品的方法,在Builder接口上是沒有的。它考慮的是最終的對象一定要通過部件構(gòu)建和裝配,才算真正創(chuàng)建了,而具體干活的就是這個Builder實現(xiàn),雖然指導(dǎo)者也參與了,但是指導(dǎo)者是不負(fù)責(zé)具體的部件創(chuàng)建和組裝的,因此客戶端是從Builder實現(xiàn)里面獲取最終裝配好的產(chǎn)品。

當(dāng)然在Java里面,我們也可以把這個方法添加到Builder接口里面。

  1. 關(guān)于被構(gòu)建的產(chǎn)品的接口

在使用生成器模式的時候,大多數(shù)情況下是不知道最終構(gòu)建出來的產(chǎn)品是什么樣的,所以在標(biāo)準(zhǔn)的生成器模式里面,一般是不需要對產(chǎn)品定義抽象接口的,因為最終構(gòu)造的產(chǎn)品千差萬別,給這些產(chǎn)品定義公共接口幾乎是沒有意義的。

3.3 使用生成器模式構(gòu)建復(fù)雜對象##

考慮這樣一個實際應(yīng)用,要創(chuàng)建一個保險合同的對象,里面很多屬性的值都有約束,要求創(chuàng)建出來的對象是滿足這些約束規(guī)則的。約束規(guī)則比如:保險合同通常情況下可以和個人簽訂,也可以和某個公司簽訂,但是一份保險合同不能同時與個人和公司簽訂。這個對象里面有很多類似這樣的約束,那么該如何來創(chuàng)建這個對象呢?

要想簡潔直觀、安全性好、又具有很好的擴展性的來創(chuàng)建這個對象的話,一個很好的選擇就是使用Builder模式,把復(fù)雜的創(chuàng)建過程通過buidler來實現(xiàn)

采用Builder模式來構(gòu)建復(fù)雜的對象,通常會對Builder模式進(jìn)行一定的簡化,因為目標(biāo)明確,就是創(chuàng)建某個復(fù)雜對象,因此做適當(dāng)簡化會使程序更簡潔,大致簡化如下:

由于是用Builder模式來創(chuàng)建某個對象,因此就沒有必要再定義一個Builder接口,直接提供一個具體的構(gòu)建器類就可以了;

對于創(chuàng)建一個復(fù)雜的對象,可能會有很多種不同的選擇和步驟,干脆去掉“指導(dǎo)者”,把指導(dǎo)者的功能和Client的功能合并起來,也就是說,Client這個時候就相當(dāng)于指導(dǎo)者,它來指導(dǎo)構(gòu)建器類去構(gòu)建需要的復(fù)雜對象;

還是來看看示例會比較清楚,為了實例簡單,先不去考慮約束的實現(xiàn),只是考慮如何通過Builder模式來構(gòu)建復(fù)雜對象。

  1. 使用Builder模式來構(gòu)建復(fù)雜對象,先不考慮帶約束

(1)先看一下保險合同的對象,示例代碼如下:

/**
   * 保險合同的對象
   */
public class InsuranceContract {
    /**
     * 保險合同編號
     */
    private String contractId;
    /**
     * 被保險人員的名稱,同一份保險合同,要么跟人員簽訂,要么跟公司簽訂,
     * 也就是說,"被保險人員"和"被保險公司"這兩個屬性,不可能同時有值
     */
    private String personName;
    /**
     * 被保險公司的名稱
     */
    private String companyName;
    /**
     * 保險開始生效的日期
     */
    private long beginDate;
    /**
     * 保險失效的日期,一定會大于保險開始生效的日期
     */
    private long endDate;
    /**
     * 示例:其它數(shù)據(jù)
     */
    private String otherData;

    /**
     * 構(gòu)造方法,訪問級別是同包能訪問
     */
    InsuranceContract(ConcreteBuilder builder){
       this.contractId = builder.getContractId();
       this.personName = builder.getPersonName();
       this.companyName = builder.getCompanyName();
       this.beginDate = builder.getBeginDate();
       this.endDate = builder.getEndDate();
       this.otherData = builder.getOtherData();
    }

    /**
     * 示意:保險合同的某些操作
     */
    public void someOperation(){
       System.out.println("Now in Insurance Contract someOperation=="+this.contractId);
    }
}

注意上例中的構(gòu)造方法是default的訪問權(quán)限,也就是不希望外部的對象直接通過new來構(gòu)建保險合同對象;另外構(gòu)造方法傳入的是構(gòu)建器對象,里面包含有所有保險合同需要的數(shù)據(jù)。

(2)看一下具體的構(gòu)建器的實現(xiàn),示例代碼如下:

/**
  * 構(gòu)造保險合同對象的構(gòu)建器
  */
public class ConcreteBuilder {
    private String contractId;
    private String personName;
    private String companyName;
    private long beginDate;
    private long endDate;
    private String otherData;
    /**
     * 構(gòu)造方法,傳入必須要有的參數(shù)
     * @param contractId 保險合同編號
     * @param beginDate 保險開始生效的日期
     * @param endDate 保險失效的日期
     */
    public ConcreteBuilder(String contractId,long beginDate,long endDate){
       this.contractId = contractId;
       this.beginDate = beginDate;
       this.endDate = endDate;
    }
    /**
     * 選填數(shù)據(jù),被保險人員的名稱
     * @param personName  被保險人員的名稱
     * @return 構(gòu)建器對象
     */
    public ConcreteBuilder setPersonName(String personName){
       this.personName = personName;
       return this;
    }
    /**
     *  選填數(shù)據(jù),被保險公司的名稱
     * @param companyName 被保險公司的名稱
     * @return 構(gòu)建器對象
     */
    public ConcreteBuilder setCompanyName(String companyName){
       this.companyName = companyName;
       return this;
    }
    /**
     * 選填數(shù)據(jù),其它數(shù)據(jù)
     * @param otherData 其它數(shù)據(jù)
     * @return 構(gòu)建器對象
     */
    public ConcreteBuilder setOtherData(String otherData){
       this.otherData = otherData;
       return this;
    }
    /**
     * 構(gòu)建真正的對象并返回
     * @return 構(gòu)建的保險合同的對象
     */
    public InsuranceContract build(){
       return new InsuranceContract(this);
    }

    public String getContractId() {
       return contractId;
    }
    public String getPersonName() {
       return personName;
    }
    public String getCompanyName() {
       return companyName;
    }
    public long getBeginDate() {
       return beginDate;
    }
    public long getEndDate() {
       return endDate;
    }
    public String getOtherData() {
       return otherData;
    }
}

注意上例中,構(gòu)建器提供了類似于setter的方法,來供外部設(shè)置需要的參數(shù),為何說是類似于setter方法呢?請注意觀察,每個這種方法都有返回值,返回的是構(gòu)建器對象,這樣客戶端就可以通過連綴的方式來使用Builder,以創(chuàng)建他們需要的對象。

(3)接下來看看此時的Client,如何使用上面的構(gòu)建器來創(chuàng)建保險合同對象,示例代碼如下:

public class Client {
    public static void main(String[] args) {
       //創(chuàng)建構(gòu)建器
       ConcreteBuilder builder = new ConcreteBuilder("001",12345L,67890L);
       //設(shè)置需要的數(shù)據(jù),然后構(gòu)建保險合同對象
       InsuranceContract contract = builder.setPersonName("張三").setOtherData("test").build();
       //操作保險合同對象的方法
       contract.someOperation();
    }
}

運行結(jié)果如下:

Now in Insurance Contract someOperation==001

看起來通過Builder模式構(gòu)建對象也很簡單,接下來,把約束加上去,看看如何實現(xiàn)。

  1. 使用Builder模式來構(gòu)建復(fù)雜對象,考慮帶約束規(guī)則

要帶著約束規(guī)則構(gòu)建復(fù)雜對象,大致的實現(xiàn)步驟與剛才的實現(xiàn)并沒有什么不同,只是需要在剛才的實現(xiàn)上把約束規(guī)則添加上去。

通常有兩個地方可以添加約束規(guī)則:

一個是構(gòu)建器的每一個類似于setter的方法,可以在這里進(jìn)行單個數(shù)據(jù)的約束規(guī)則校驗,如果不正確,就拋出IllegalStateException;

另一個是構(gòu)建器的build方法,在創(chuàng)建保險合同對象之前,對所有的數(shù)據(jù)都可以進(jìn)行數(shù)據(jù)的約束規(guī)則校驗,尤其是那些涉及到幾個數(shù)據(jù)之間的約束關(guān)系,在這里校驗會比較合適。如果不正確,同樣拋出IllegalStateException;

這里選擇在構(gòu)建器的build方法里面,進(jìn)行數(shù)據(jù)的整體校驗,由于其它的代碼都沒有變化,因此就不去贅述了,新的build方法的示例代碼如下:

/**
 * 構(gòu)建真正的對象并返回
 * @return 構(gòu)建的保險合同的對象
 */
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);
}

你可以修改客戶端的構(gòu)建代碼,傳入不同的數(shù)據(jù),看看這些約束規(guī)則是否能夠正常工作,當(dāng)然類似的規(guī)則還有很多,這里就不去深究了。

  1. 進(jìn)一步,把構(gòu)建器對象和被構(gòu)建對象合并

其實,在實際開發(fā)中,如果構(gòu)建器對象和被構(gòu)建的對象是這樣分開的話,可能會導(dǎo)致同包內(nèi)的對象不使用構(gòu)建器來構(gòu)建對象,而是直接去使用new來構(gòu)建對象,這會導(dǎo)致錯誤;另外,這個構(gòu)建器的功能就是為了創(chuàng)建被構(gòu)建的對象,完全可以不用單獨一個類。

對于這種情況,重構(gòu)的手法通常是將類內(nèi)聯(lián)化(Inline Class),放到這里來,簡單點說就是把構(gòu)建器對象合并到被構(gòu)建對象里面去

還是看看示例會比較清楚,示例代碼如下:

public class InsuranceContract {
    private String contractId;
    private String personName;
    private String companyName;
    private long beginDate;
    private long endDate;
    private String otherData;

    /**
     * 構(gòu)造方法,訪問級別是私有的
     */
    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;
    }

    /**
     * 構(gòu)造保險合同對象的構(gòu)建器,作為保險合同的類級內(nèi)部類
     */
    public static class ConcreteBuilder {
       private String contractId;
       private String personName;
       private String companyName;
       private long beginDate;
       private long endDate;
       private String otherData;
       /**
        * 構(gòu)造方法,傳入必須要有的參數(shù)
        * @param contractId 保險合同編號
        * @param beginDate 保險開始生效的日期
        * @param endDate 保險失效的日期
        */
       public ConcreteBuilder(String contractId,long beginDate,long endDate){
           this.contractId = contractId;
           this.beginDate = beginDate;
           this.endDate = endDate;
       }
       /**
        * 選填數(shù)據(jù),被保險人員的名稱
        * @param personName  被保險人員的名稱
        * @return 構(gòu)建器對象
        */
       public ConcreteBuilder setPersonName(String personName){
           this.personName = personName;
           return this;
       }
       /**
        *  選填數(shù)據(jù),被保險公司的名稱
        * @param companyName 被保險公司的名稱
        * @return 構(gòu)建器對象
        */
       public ConcreteBuilder setCompanyName(String companyName){
           this.companyName = companyName;
           return this;
       }
       /**
        * 選填數(shù)據(jù),其它數(shù)據(jù)
        * @param otherData 其它數(shù)據(jù)
        * @return 構(gòu)建器對象
        */
       public ConcreteBuilder setOtherData(String otherData){
           this.otherData = otherData;
           return this;
       }
       /**
        * 構(gòu)建真正的對象并返回
        * @return 構(gòu)建的保險合同的對象
        */
       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 void someOperation(){
       System.out.println("Now in Insurance Contract someOperation=="+this.contractId);
    }
}

通過上面的示例可以看出,這種實現(xiàn)方式會更簡單和直觀。此時客戶端的寫法也發(fā)生了一點變化,主要就是創(chuàng)建構(gòu)造器的地方需要變化,示例代碼如下:

public class Client {
    public static void main(String[] args) {
       //創(chuàng)建構(gòu)建器
       InsuranceContract.ConcreteBuilder builder = new InsuranceContract.ConcreteBuilder("001",12345L,67890L);
       //設(shè)置需要的數(shù)據(jù),然后構(gòu)建保險合同對象
       InsuranceContract contract = builder.setPersonName("張三").setOtherData("test").build();
       //操作保險合同對象的方法
       contract.someOperation();
    }
}

3.4 生成器模式的優(yōu)缺點##

  1. 松散耦合

生成器模式可以用同一個構(gòu)建算法,構(gòu)建出表現(xiàn)上完全不同的產(chǎn)品,實現(xiàn)產(chǎn)品構(gòu)建和產(chǎn)品表現(xiàn)上的分離。生成器模式正是把產(chǎn)品構(gòu)建的過程獨立出來,使它和具體產(chǎn)品的表現(xiàn)松散耦合,從而使得構(gòu)建算法可以復(fù)用,而具體產(chǎn)品表現(xiàn)也可以靈活的、方便的擴展和切換。

  1. 可以很容易的改變產(chǎn)品的內(nèi)部表示

在生成器模式中,由于Builder對象只是提供接口給Director使用,那么具體的部件創(chuàng)建和裝配方式是被Builder接口隱藏了的,Director并不知道這些具體的實現(xiàn)細(xì)節(jié)。這樣一來,要想改變產(chǎn)品的內(nèi)部表示,只需要切換Builder的具體實現(xiàn)即可,不用管Director,因此變得很容易。

  1. 更好的復(fù)用性

生成器模式很好的實現(xiàn)了構(gòu)建算法和具體產(chǎn)品實現(xiàn)的分離,這樣一來,使得構(gòu)建產(chǎn)品的算法可以復(fù)用。同樣的道理,具體產(chǎn)品的實現(xiàn)也可以復(fù)用,同一個產(chǎn)品的實現(xiàn),可以配合不同的構(gòu)建算法使用。

3.5 思考生成器模式##

  1. 生成器模式的本質(zhì)

生成器模式的本質(zhì):分離整體構(gòu)建算法和部件構(gòu)造。

構(gòu)建一個復(fù)雜的對象,本來就有構(gòu)建的過程,以及構(gòu)建過程中具體的實現(xiàn),生成器模式就是用來分離這兩個部分,從而使得程序結(jié)構(gòu)更松散、擴展更容易、復(fù)用性更好,同時也會使得代碼更清晰,意圖更明確。

雖然在生成器模式的整體構(gòu)建算法中,會一步一步引導(dǎo)Builder來構(gòu)建對象,但這并不是說生成器就主要是用來實現(xiàn)分步驟構(gòu)建對象的。生成器模式的重心還是在于分離整體構(gòu)建算法和部件構(gòu)造,而分步驟構(gòu)建對象不過是整體構(gòu)建算法的一個簡單表現(xiàn),或者說是一個附帶產(chǎn)物

  1. 何時選用生成器模式

建議在如下情況中,選用生成器模式:

如果創(chuàng)建對象的算法,應(yīng)該獨立于該對象的組成部分以及它們的裝配方式時;

如果同一個構(gòu)建過程有著不同的表示時;

3.6 相關(guān)模式##

  1. 生成器模式和工廠方法模式

這兩個模式可以組合使用。

生成器模式的Builder實現(xiàn)中,通常需要選擇具體的部件實現(xiàn),一個可行的方案就是實現(xiàn)成為工廠方法,通過工廠方法來獲取具體的部件對象,然后再進(jìn)行部件的裝配。

  1. 生成器模式和抽象工廠模式

這兩個模式既相似又有區(qū)別,也可以組合使用

說說區(qū)別:抽象工廠模式的主要目的是創(chuàng)建產(chǎn)品簇,這個產(chǎn)品簇里面的單個產(chǎn)品,就相當(dāng)于是構(gòu)成一個復(fù)雜對象的部件對象,抽象工廠對象創(chuàng)建完成過后就立即返回整個產(chǎn)品簇;而生成器模式的主要目的是按照構(gòu)造算法,一步一步來構(gòu)建一個復(fù)雜的產(chǎn)品對象,通常要等到整個構(gòu)建過程結(jié)束過后,才會得到最終的產(chǎn)品對象。

事實上,這兩個模式是可以組合使用的,在生成器模式的Builder實現(xiàn)中,需要創(chuàng)建各個部件對象,而這些部件對象是有關(guān)聯(lián)的,通常是構(gòu)成一個復(fù)雜對象的部件對象,也就是說,Builder實現(xiàn)中,需要獲取構(gòu)成一個復(fù)雜對象的產(chǎn)品簇,那自然就可以使用抽象工廠模式來實現(xiàn)。這樣一來,由抽象工廠模式負(fù)責(zé)了部件對象的創(chuàng)建,Builder實現(xiàn)里面就主要負(fù)責(zé)產(chǎn)品對象整體的構(gòu)建了。

  1. 生成器模式和模板方法模式

這也是兩個非常類似的模式。初看之下,不會覺得這兩個模式有什么關(guān)聯(lián),但是仔細(xì)一思考,發(fā)現(xiàn)兩個模式在功能上很類似。

模板方法模式主要是用來定義算法的骨架,把算法中某些步驟延遲到子類中實現(xiàn)。再想想生成器模式,Director用來定義整體的構(gòu)建算法,把算法中某些涉及到具體部件對象的創(chuàng)建和裝配的功能,委托給具體的Builder來實現(xiàn)

雖然生成器不是延遲到子類,是委托給Builder,但那只是具體實現(xiàn)方式上的差別,從實質(zhì)上看兩個模式很類似,都是定義一個固定的算法骨架,然后把算法中的某些具體步驟交給其它類來完成,都能實現(xiàn)整體算法步驟和某些具體步驟實現(xiàn)的分離。

當(dāng)然兩個模式也有很大的區(qū)別:

  1. 首先是模式的目的,生成器模式是用來構(gòu)建復(fù)雜對象的,而模板方法是用來定義算法骨架,尤其是一些復(fù)雜的業(yè)務(wù)功能的處理算法的骨架;
  2. 其次是模式的實現(xiàn),生成器模式是采用委托的方法,而模板方法是采用的繼承的方式;
  3. 另外從使用的復(fù)雜度上,生成器模式需要組合Director和Builder對象,然后才能開始構(gòu)建,要等構(gòu)建完后才能獲得最終的對象,而模板方法就沒有這么麻煩,直接使用子類對象即可。
  1. 生成器模式和組合模式

這兩個模式可以組合使用。

對于復(fù)雜的組合結(jié)構(gòu),可以使用生成器模式來一步一步構(gòu)建。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內(nèi)容