#0 系列目錄#算法
#1 場景問題# ##1.1 繼續導出數據的應用框架## 在討論工廠方法模式的時候,提到了一個導出數據的應用框架。數據庫
對於導出數據的應用框架,一般在導出數據上,會有一些約定的方式,好比導出成:文本格式、數據庫備份形式、Excel格式、Xml格式等等。設計模式
在工廠方法模式章節裏面,討論並使用工廠方法模式來解決了如何選擇具體導出方式的問題,並無涉及到每種方式具體如何實現。換句話說,在討論工廠方法模式的時候,並無討論如何實現導出成文本、Xml等具體的格式
,本章就來討論這個問題。安全
對於導出數據的應用框架,一般對於具體的導出內容和格式是有要求的,假如如今有以下的要求,簡單描述一下:app
導出的文件,無論什麼格式,都分紅三個部分,分別是文件頭、文件體和文件尾框架
在文件頭部分,須要描述以下信息:分公司或門市點編號、導出數據的日期,對於文本格式,中間用逗號分隔測試
在文件體部分,須要描述以下信息:表名稱、而後分條描述數據。對於文本格式,表名稱單獨佔一行,數據描述一行算一條數據,字段間用逗號分隔。ui
在文件尾部分,須要描述以下信息:輸出人this
如今就要來實現上述功能。爲了演示簡單點,在工廠方法模式裏面已經實現的功能,這裏就不去重複了,這裏只關心如何實現導出文件,並且只實現導出成文本格式和XML格式就能夠了,其它的就不去考慮了。.net
##1.2 不用模式的解決方案## 不就是要實現導出數據到文本文件和XML文件嗎,其實無論什麼格式,須要導出的數據是同樣的,只是具體導出到文件中的內容,會隨着格式的不一樣而不一樣。
/** * 描述輸出到文件頭的內容的對象 */ public class ExportHeaderModel { /** * 分公司或門市點編號 */ private String depId; /** * 導出數據的日期 */ 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; } }
接下來看看描述輸出數據的對象,示例代碼以下:
/** * 描述輸出數據的對象 */ public class ExportDataModel { /** * 產品編號 */ private String productId; /** * 銷售價格 */ private double price; /** * 銷售數量 */ 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; } }
接下來看看描述輸出到文件尾的內容的對象,示例代碼以下:
/** * 描述輸出到文件尾的內容的對象 */ public class ExportFooterModel { /** * 輸出人 */ private String exportUser; public String getExportUser() { return exportUser; } public void setExportUser(String exportUser) { this.exportUser = exportUser; } }
/** * 導出數據到文本文件的對象 */ public class ExportToTxt { /** * 導出數據到文本文件 * @param ehm 文件頭的內容 * @param mapData 數據的內容 * @param efm 文件尾的內容 */ public void export(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm){ //用來記錄最終輸出的文件內容 StringBuffer buffer = new StringBuffer(); //1:先來拼接文件頭的內容 buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n"); //2:接着來拼接文件體的內容 for(String tblName : mapData.keySet()){ //先拼接表名稱 buffer.append(tblName+"\n"); //而後循環拼接具體數據 for(ExportDataModel edm : mapData.get(tblName)){ buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n"); } } //3:接着來拼接文件尾的內容 buffer.append(efm.getExportUser()); //爲了演示簡潔性,這裏就不去寫輸出文件的代碼了 //把要輸出的內容輸出到控制檯看看 System.out.println("輸出到文本文件的內容:\n"+buffer); } }
/** * 導出數據到XML文件的對象 */ public class ExportToXml { /** * 導出數據到XML文件 * @param ehm 文件頭的內容 * @param mapData 數據的內容 * @param efm 文件尾的內容 */ public void export(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm){ //用來記錄最終輸出的文件內容 StringBuffer buffer = new StringBuffer(); //1:先來拼接文件頭的內容 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:接着來拼接文件體的內容 buffer.append(" <Body>\n"); for(String tblName : mapData.keySet()){ //先拼接表名稱 buffer.append(" <Datas TableName=\""+tblName+"\">\n"); //而後循環拼接具體數據 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:接着來拼接文件尾的內容 buffer.append(" <Footer>\n"); buffer.append(" <ExportUser>"+efm.getExportUser()+"</ExportUser>\n"); buffer.append(" </Footer>\n"); buffer.append("</Report>\n"); //爲了演示簡潔性,這裏就不去寫輸出文件的代碼了 //把要輸出的內容輸出到控制檯看看 System.out.println("輸出到XML文件的內容:\n"+buffer); } }
public class Client { public static void main(String[] args) { //準備測試數據 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("產品001號"); edm1.setPrice(100); edm1.setAmount(80); ExportDataModel edm2 = new ExportDataModel(); edm2.setProductId("產品002號"); edm2.setPrice(99); edm2.setAmount(55); //把數據組裝起來 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); } }
運行結果以下:
##1.3 有何問題## 仔細觀察上面的實現,會發現,無論是輸出成文本文件,仍是輸出到XML文件,在實現的時候,步驟基本上都是同樣的,都大體分紅了以下四步:
先拼接文件頭的內容
而後拼接文件體的內容
再拼接文件尾的內容
最後把拼接好的內容輸出出去成爲文件
也就是說,對於不一樣的輸出格式,處理步驟是同樣的,可是具體每步的實現是不同的
。按照如今的實現方式,就存在以下的問題:
(1)構建每種輸出格式的文件內容的時候,都會重複這幾個處理步驟,應該提煉出來,造成公共的處理過程;
(2)從此可能會有不少不一樣輸出格式的要求,這就須要在處理過程不變的狀況下,能方便的切換不一樣的輸出格式的處理;
換句話來講,也就是構建每種格式的數據文件的處理過程,應該和具體的步驟實現分開,這樣就可以複用處理過程,並且能很容易的切換不一樣的輸出格式
。
但是該如何實現呢?
#2 解決方案# ##2.1 生成器模式來解決## 用來解決上述問題的一個合理的解決方案就是生成器模式。那麼什麼是生成器模式呢?
仔細分析上面的實現,構建每種格式的數據文件的處理過程,這不就是構建過程嗎?而每種格式具體的步驟實現,不就至關因而不一樣的表示嗎?由於不一樣的步驟實現,決定了最終的表現也就不一樣
。也就是說,上面的問題剛好就是生成器模式要解決的問題。
要實現一樣的構建過程能夠建立不一樣的表現,那麼一個天然的思路就是先把構建過程獨立出來,在生成器模式中把它稱爲指導者,由它來指導裝配過程,可是不負責每步具體的實現
。固然,光有指導者是不夠的,必需要有能具體實現每步的對象,在生成器模式中稱這些實現對象爲生成器
。
這樣一來,指導者就是能夠重用的構建過程,而生成器是能夠被切換的具體實現
。前面的實現中,每種具體的導出文件格式的實現就至關於生成器。
##2.2 模式結構和說明## 生成器模式的結構如圖8.1所示。
Builder:生成器接口,定義建立一個Product對象所需的各個部件的操做。
ConcreteBuilder:具體的生成器實現,實現各個部件的建立,並負責組裝Product對象的各個部件,同時還提供一個讓用戶獲取組裝完成後的產品對象的方法。
Director:指導者,也被稱爲導向者,主要用來使用Builder接口,以一個統一的過程來構建所須要的Product對象。
Product:產品,表示被生成器構建的複雜對象,包含多個部件。
##2.3 生成器模式示例代碼##
/** * 生成器接口,定義建立一個產品對象所需的各個部件的操做 */ public interface Builder { /** * 示意方法,構建某個部件 */ public void buildPart(); }
/** * 具體的生成器實現對象 */ public class ConcreteBuilder implements Builder { /** * 生成器最終構建的產品對象 */ private Product resultProduct; /** * 獲取生成器最終構建的產品對象 * @return 生成器最終構建的產品對象 */ public Product getResult() { return resultProduct; } public void buildPart() { //構建某個部件的功能處理 } }
/** * 被構建的產品對象的接口 */ public interface Product { //定義產品的操做 }
/** * 指導者,指導使用生成器的接口來構建產品的對象 */ public class Director { /** * 持有當前須要使用的生成器對象 */ private Builder builder; /** * 構造方法,傳入生成器對象 * @param builder 生成器對象 */ public Director(Builder builder) { this.builder = builder; } /** * 示意方法,指導生成器構建最終的產品對象 */ public void construct() { //經過使用生成器接口來構建最終的產品對象 builder.buildPart(); } }
##2.4 使用生成器模式重寫示例## 要使用生成器模式來重寫示例,重要的任務就是要把指導者和生成器接口定義出來
。指導者就是用來執行那四個步驟的對象,而生成器是用來實現每種格式下,對於每一個步驟的具體實現的對象
。
按照生成器模式重寫示例的結構如圖8.2所示:
前面示例中的三個數據模型對象還繼續沿用,這裏就不去贅述了。
先來看看定義的Builder接口,主要是把導出各類格式文件的處理過程的步驟定義出來,每一個步驟負責構建最終導出文件的一部分。示例代碼以下:
/** * 生成器接口,定義建立一個輸出文件對象所需的各個部件的操做 */ public interface Builder { /** * 構建輸出文件的Header部分 * @param ehm 文件頭的內容 */ public void buildHeader(ExportHeaderModel ehm); /** * 構建輸出文件的Body部分 * @param mapData 要輸出的數據的內容 */ public void buildBody(Map<String,Collection<ExportDataModel>> mapData); /** * 構建輸出文件的Footer部分 * @param efm 文件尾的內容 */ public void buildFooter(ExportFooterModel efm); }
/** * 實現導出數據到文本文件的的生成器對象 */ public class TxtBuilder implements Builder { /** * 用來記錄構建的文件的內容,至關於產品 */ private StringBuffer buffer = new StringBuffer(); public void buildBody(Map<String, Collection<ExportDataModel>> mapData) { for(String tblName : mapData.keySet()){ //先拼接表名稱 buffer.append(tblName+"\n"); //而後循環拼接具體數據 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; } }
再看看導出數據到XML文件的生成器實現,示例代碼以下:
/** * 實現導出數據到XML文件的的生成器對象 */ public class XmlBuilder implements Builder { /** * 用來記錄構建的文件的內容,至關於產品 */ 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"); //而後循環拼接具體數據 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; } }
有了具體的生成器實現後,須要有指導者來指導它進行具體的產品構建,因爲構建的產品是文本內容,因此就不用單獨定義產品對象了。示例代碼以下:
/** * 指導者,指導使用生成器的接口來構建輸出的文件的對象 */ public class Director { /** * 持有當前須要使用的生成器對象 */ private Builder builder; /** * 構造方法,傳入生成器對象 * @param builder 生成器對象 */ public Director(Builder builder) { this.builder = builder; } /** * 指導生成器構建最終的輸出的文件的對象 * @param ehm 文件頭的內容 * @param mapData 數據的內容 * @param efm 文件尾的內容 */ public void construct(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> mapData,ExportFooterModel efm) { //1:先構建Header builder.buildHeader(ehm); //2:而後構建Body builder.buildBody(mapData); //3:而後構建Footer builder.buildFooter(efm); } }
public class Client { public static void main(String[] args) { //準備測試數據 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("產品001號"); edm1.setPrice(100); edm1.setAmount(80); ExportDataModel edm2 = new ExportDataModel(); edm2.setProductId("產品002號"); edm2.setPrice(99); edm2.setAmount(55); //把數據組裝起來 col.add(edm1); col.add(edm2); mapData.put("銷售記錄表", col); ExportFooterModel efm = new ExportFooterModel(); efm.setExportUser("張三"); //測試輸出到文本文件 TxtBuilder txtBuilder = new TxtBuilder(); //建立指導者對象 Director director = new Director(txtBuilder); director.construct(ehm, mapData, efm); //把要輸出的內容輸出到控制檯看看 System.out.println("輸出到文本文件的內容:\n"+txtBuilder.getResult()); //測試輸出到xml文件 XmlBuilder xmlBuilder = new XmlBuilder(); Director director2 = new Director(xmlBuilder); director2.construct(ehm, mapData, efm); //把要輸出的內容輸出到控制檯看看 System.out.println("輸出到XML文件的內容:\n"+xmlBuilder.getResult()); } }
看了上面的示例會發現,其實生成器模式也挺簡單的,好好理解一下。經過上面的講述,應該能很清晰的看出生成器模式的實現方式和它的優點所在了,那就是對同一個構建過程,只要配置不一樣的生成器實現,就會生成出不一樣表現的對象
。
#3 模式講解# ##3.1 認識生成器模式##
生成器模式的主要功能是構建複雜的產品,並且是細化的,分步驟的構建產品,也就是生成器模式重在解決一步一步構造複雜對象的問題
。若是光是這麼認識生成器模式的功能是不夠的。
更爲重要的是,這個構建的過程是統一的,固定不變的,變化的部分放到生成器部分了,只要配置不一樣的生成器,那麼一樣的構建過程,就能構建出不一樣的產品表示來
。
再直白點說,生成器模式的重心在於分離構建算法和具體的構造實現,從而使得構建算法能夠重用,具體的構造實現能夠很方便的擴展和切換
,從而能夠靈活的組合來構造出不一樣的產品對象。
要特別注意,生成器模式分紅兩個很重要的部分:
一個部分是Builder接口這邊,這邊是定義瞭如何構建各個部件,也就是知道每一個部件功能如何實現,以及如何裝配這些部件到產品中去;
另一個部分是Director這邊,Director是知道如何組合來構建產品,也就是說Director負責總體的構建算法,並且一般是分步驟的來執行。
無論如何變化,Builder模式都存在這麼兩個部分,一個部分是部件構造和產品裝配,另外一個部分是總體構建的算法
。認識這點是很重要的,由於在生成器模式中,強調的是固定總體構建的算法,而靈活擴展和切換部件的具體構造和產品裝配的方式,因此要嚴格區分這兩個部分。
在Director實現總體構建算法的時候,遇到須要建立和組合具體部件的時候,就會把這些功能經過委託,交給Builder去完成。
應用生成器模式的時候,可讓客戶端創造Director,在Director裏面封裝總體構建算法,而後讓Director去調用Builder,讓Builder來封裝具體部件的構建功能
,這就跟前面的例子同樣。
還有一種退化的狀況,就是讓客戶端和Director融合起來,讓客戶端直接去操做Builder,就好像是指導者本身想要給本身構建產品同樣
。
##3.2 生成器模式的實現##
實際上在Builder接口的實現中,每一個部件構建的方法裏面,除了部件裝配外,也能夠實現如何具體的建立各個部件對象,也就是說每一個方法均可以有兩部分功能,一個是建立部件對象,一個是組裝部件
。
在構建部件的方法裏面能夠實現選擇並建立具體的部件對象,而後再把這個部件對象組裝到產品對象中去,這樣一來,Builder就能夠和工廠方法配合使用了
。
再進一步,若是在實現Builder的時候,只有建立對象的功能,而沒有組裝的功能,那麼這個時候的Builder實現跟抽象工廠的實現是相似的
。
這種狀況下,Builder接口就相似於抽象工廠的接口,Builder的具體實現就相似於具體的工廠
,並且Builder接口裏面定義的建立各個部件的方法也是有關聯的,這些方法是構建一個複雜對象所須要的部件對象
,仔細想一想,是否是很是相似呢。
在生成器模式裏面,指導者承擔的是總體構建算法部分,是相對不變的部分
。所以在實現指導者的時候,把變化的部分分離出去是很重要的
。
其實指導者分離出去的變化部分,就到了生成器那邊,指導者知道總體的構建算法,就是不知道如何具體的建立和裝配部件對象
。
所以真正的指導者實現,並不只僅是如同前面示例那樣,簡單的按照必定順序調用生成器的方法來生成對象,並無這麼簡單。應該是有較爲複雜的算法和運算過程,在運算過程當中根據須要,纔會調用生成器的方法來生成部件對象
。
在生成器模式裏面,指導者和生成器的交互,是經過生成器的那些buildPart方法來完成的。在前面的示例中,指導者和生成器是沒有太多相互交互的,指導者僅僅只是簡單的調用了一下生成器的方法,在實際開發中,這是遠遠不夠的。
指導者一般會實現比較複雜的算法或者是運算過程,在實際中極可能會有這樣的狀況:
在運行指導者的時候,會按照總體構建算法的步驟進行運算,可能先運行前幾步運算,到了某一步驟,須要具體建立某個部件對象了,而後就調用Builder中建立相應部件的方法來建立具體的部件。同時,把前面運算獲得的數據傳遞給Builder,由於在Builder內部實現建立和組裝部件的時候,可能會須要這些數據;
Builder建立完具體的部件對象後,會把建立好的部件對象返回給指導者,指導者繼續後續的算法運算,可能會用到已經建立好的對象;
如此反覆下去,直到整個構建算法運行完成,那麼最終的產品對象也就建立好了;
經過上面的描述,能夠看出指導者和生成器是須要交互的,方式就是經過生成器方法的參數和返回值,來回的傳遞數據。事實上,指導者是經過委託的方式來把功能交給生成器去完成
。
在標準的生成器模式裏面,在Builder實現裏面會提供一個返回裝配好的產品的方法,在Builder接口上是沒有的
。它考慮的是最終的對象必定要經過部件構建和裝配,纔算真正建立了,而具體幹活的就是這個Builder實現
,雖然指導者也參與了,可是指導者是不負責具體的部件建立和組裝的,所以客戶端是從Builder實現裏面獲取最終裝配好的產品。
固然在Java裏面,咱們也能夠把這個方法添加到Builder接口裏面。
在使用生成器模式的時候,大多數狀況下是不知道最終構建出來的產品是什麼樣的
,因此在標準的生成器模式裏面,通常是不須要對產品定義抽象接口的,由於最終構造的產品千差萬別,給這些產品定義公共接口幾乎是沒有意義的。
##3.3 使用生成器模式構建複雜對象## 考慮這樣一個實際應用,要建立一個保險合同的對象,裏面不少屬性的值都有約束,要求建立出來的對象是知足這些約束規則的。約束規則好比:保險合同一般狀況下能夠和我的簽定,也能夠和某個公司簽定,可是一份保險合同不能同時與我的和公司簽定。這個對象裏面有不少相似這樣的約束,那麼該如何來建立這個對象呢?
要想簡潔直觀、安全性好、又具備很好的擴展性的來建立這個對象的話,一個很好的選擇就是使用Builder模式,把複雜的建立過程經過buidler來實現
。
採用Builder模式來構建複雜的對象,一般會對Builder模式進行必定的簡化,由於目標明確,就是建立某個複雜對象,所以作適當簡化會使程序更簡潔
,大體簡化以下:
因爲是用Builder模式來建立某個對象,所以就沒有必要再定義一個Builder接口,直接提供一個具體的構建器類就能夠了;
對於建立一個複雜的對象,可能會有不少種不一樣的選擇和步驟,乾脆去掉「指導者」,把指導者的功能和Client的功能合併起來
,也就是說,Client這個時候就至關於指導者,它來指導構建器類去構建須要的複雜對象;
仍是來看看示例會比較清楚,爲了實例簡單,先不去考慮約束的實現,只是考慮如何經過Builder模式來構建複雜對象。
(1)先看一下保險合同的對象,示例代碼以下:
/** * 保險合同的對象 */ public class InsuranceContract { /** * 保險合同編號 */ private String contractId; /** * 被保險人員的名稱,同一份保險合同,要麼跟人員簽定,要麼跟公司簽定, * 也就是說,"被保險人員"和"被保險公司"這兩個屬性,不可能同時有值 */ private String personName; /** * 被保險公司的名稱 */ private String companyName; /** * 保險開始生效的日期 */ private long beginDate; /** * 保險失效的日期,必定會大於保險開始生效的日期 */ private long endDate; /** * 示例:其它數據 */ private String otherData; /** * 構造方法,訪問級別是同包能訪問 */ 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); } }
注意上例中的構造方法是default的訪問權限,也就是不但願外部的對象直接經過new來構建保險合同對象
;另外構造方法傳入的是構建器對象,裏面包含有全部保險合同須要的數據
。
(2)看一下具體的構建器的實現,示例代碼以下:
/** * 構造保險合同對象的構建器 */ public 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; } /** * 選填數據,被保險人員的名稱 * @param personName 被保險人員的名稱 * @return 構建器對象 */ public ConcreteBuilder setPersonName(String personName){ this.personName = personName; return this; } /** * 選填數據,被保險公司的名稱 * @param companyName 被保險公司的名稱 * @return 構建器對象 */ public ConcreteBuilder setCompanyName(String companyName){ this.companyName = companyName; return this; } /** * 選填數據,其它數據 * @param otherData 其它數據 * @return 構建器對象 */ public ConcreteBuilder setOtherData(String otherData){ this.otherData = otherData; return this; } /** * 構建真正的對象並返回 * @return 構建的保險合同的對象 */ 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; } }
注意上例中,構建器提供了相似於setter的方法,來供外部設置須要的參數
,爲什麼說是相似於setter方法呢?請注意觀察,每一個這種方法都有返回值,返回的是構建器對象
,這樣客戶端就能夠經過連綴的方式來使用Builder,以建立他們須要的對象。
(3)接下來看看此時的Client,如何使用上面的構建器來建立保險合同對象,示例代碼以下:
public class Client { public static void main(String[] args) { //建立構建器 ConcreteBuilder builder = new ConcreteBuilder("001",12345L,67890L); //設置須要的數據,而後構建保險合同對象 InsuranceContract contract = builder.setPersonName("張三").setOtherData("test").build(); //操做保險合同對象的方法 contract.someOperation(); } }
運行結果以下:
Now in Insurance Contract someOperation==001
看起來經過Builder模式構建對象也很簡單,接下來,把約束加上去,看看如何實現。
要帶着約束規則構建複雜對象,大體的實現步驟與剛纔的實現並無什麼不一樣,只是須要在剛纔的實現上把約束規則添加上去。
一般有兩個地方能夠添加約束規則:
一個是構建器的每個相似於setter的方法,能夠在這裏進行單個數據的約束規則校驗,若是不正確,就拋出IllegalStateException;
另外一個是構建器的build方法,在建立保險合同對象以前,對全部的數據均可以進行數據的約束規則校驗,尤爲是那些涉及到幾個數據之間的約束關係,在這裏校驗會比較合適。若是不正確,一樣拋出IllegalStateException;
這裏選擇在構建器的build方法裏面,進行數據的總體校驗,因爲其它的代碼都沒有變化,所以就不去贅述了,新的build方法的示例代碼以下:
/** * 構建真正的對象並返回 * @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); }
你能夠修改客戶端的構建代碼,傳入不一樣的數據,看看這些約束規則是否可以正常工做,固然相似的規則還有不少,這裏就不去深究了。
其實,在實際開發中,若是構建器對象和被構建的對象是這樣分開的話,可能會致使同包內的對象不使用構建器來構建對象,而是直接去使用new來構建對象,這會致使錯誤
;另外,這個構建器的功能就是爲了建立被構建的對象,徹底能夠不用單獨一個類
。
對於這種狀況,重構的手法一般是將類內聯化(Inline Class),放到這裏來,簡單點說就是把構建器對象合併到被構建對象裏面去
。
仍是看看示例會比較清楚,示例代碼以下:
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 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; } /** * 選填數據,被保險人員的名稱 * @param personName 被保險人員的名稱 * @return 構建器對象 */ public ConcreteBuilder setPersonName(String personName){ this.personName = personName; return this; } /** * 選填數據,被保險公司的名稱 * @param companyName 被保險公司的名稱 * @return 構建器對象 */ public ConcreteBuilder setCompanyName(String companyName){ this.companyName = companyName; return this; } /** * 選填數據,其它數據 * @param otherData 其它數據 * @return 構建器對象 */ 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 void someOperation(){ System.out.println("Now in Insurance Contract someOperation=="+this.contractId); } }
經過上面的示例能夠看出,這種實現方式會更簡單和直觀。此時客戶端的寫法也發生了一點變化,主要就是建立構造器的地方須要變化,示例代碼以下:
public class Client { public static void main(String[] args) { //建立構建器 InsuranceContract.ConcreteBuilder builder = new InsuranceContract.ConcreteBuilder("001",12345L,67890L); //設置須要的數據,而後構建保險合同對象 InsuranceContract contract = builder.setPersonName("張三").setOtherData("test").build(); //操做保險合同對象的方法 contract.someOperation(); } }
##3.4 生成器模式的優缺點##
生成器模式能夠用同一個構建算法,構建出表現上徹底不一樣的產品,實現產品構建和產品表現上的分離
。生成器模式正是把產品構建的過程獨立出來,使它和具體產品的表現鬆散耦合,從而使得構建算法能夠複用,而具體產品表現也能夠靈活的、方便的擴展和切換。
在生成器模式中,因爲Builder對象只是提供接口給Director使用,那麼具體的部件建立和裝配方式是被Builder接口隱藏了的,Director並不知道這些具體的實現細節。這樣一來,要想改變產品的內部表示,只須要切換Builder的具體實現便可
,不用管Director,所以變得很容易。
生成器模式很好的實現了構建算法和具體產品實現的分離
,這樣一來,使得構建產品的算法能夠複用。一樣的道理,具體產品的實現也能夠複用,同一個產品的實現,能夠配合不一樣的構建算法使用。
##3.5 思考生成器模式##
生成器模式的本質:分離總體構建算法和部件構造。
構建一個複雜的對象,原本就有構建的過程,以及構建過程當中具體的實現,生成器模式就是用來分離這兩個部分,從而使得程序結構更鬆散、擴展更容易、複用性更好,同時也會使得代碼更清晰,意圖更明確。
雖然在生成器模式的總體構建算法中,會一步一步引導Builder來構建對象,但這並非說生成器就主要是用來實現分步驟構建對象的。生成器模式的重心仍是在於分離總體構建算法和部件構造,而分步驟構建對象不過是總體構建算法的一個簡單表現,或者說是一個附帶產物
。
建議在以下狀況中,選用生成器模式:
若是建立對象的算法,應該獨立於該對象的組成部分以及它們的裝配方式時;
若是同一個構建過程有着不一樣的表示時;
##3.6 相關模式##
這兩個模式能夠組合使用。
生成器模式的Builder實現中,一般須要選擇具體的部件實現,一個可行的方案就是實現成爲工廠方法,經過工廠方法來獲取具體的部件對象,而後再進行部件的裝配
。
這兩個模式既類似又有區別,也能夠組合使用
先說類似性,這個在3.2小節的第一個小題目裏面已經詳細講述了,這裏就不去重複了。
再說說區別:抽象工廠模式的主要目的是建立產品簇
,這個產品簇裏面的單個產品,就至關因而構成一個複雜對象的部件對象,抽象工廠對象建立完成事後就當即返回整個產品簇
;而生成器模式的主要目的是按照構造算法
,一步一步來構建一個複雜的產品對象,一般要等到整個構建過程結束事後,纔會獲得最終的產品對象。
事實上,這兩個模式是能夠組合使用的,在生成器模式的Builder實現中,須要建立各個部件對象,而這些部件對象是有關聯的,一般是構成一個複雜對象的部件對象,也就是說,Builder實現中,須要獲取構成一個複雜對象的產品簇,那天然就可使用抽象工廠模式來實現
。這樣一來,由抽象工廠模式負責了部件對象的建立,Builder實現裏面就主要負責產品對象總體的構建了。
這也是兩個很是相似的模式。初看之下,不會以爲這兩個模式有什麼關聯,可是仔細一思考,發現兩個模式在功能上很相似。
模板方法模式主要是用來定義算法的骨架,把算法中某些步驟延遲到子類中實現
。再想一想生成器模式,Director用來定義總體的構建算法,把算法中某些涉及到具體部件對象的建立和裝配的功能,委託給具體的Builder來實現
。
雖然生成器不是延遲到子類,是委託給Builder,但那只是具體實現方式上的差異,從實質上看兩個模式很相似,都是定義一個固定的算法骨架,而後把算法中的某些具體步驟交給其它類來完成,都能實現總體算法步驟和某些具體步驟實現的分離。
固然兩個模式也有很大的區別,首先是模式的目的
,生成器模式是用來構建複雜對象的,而模板方法是用來定義算法骨架,尤爲是一些複雜的業務功能的處理算法的骨架;其次是模式的實現
,生成器模式是採用委託的方法,而模板方法是採用的繼承的方式;另外從使用的複雜度上,生成器模式須要組合Director和Builder對象,而後才能開始構建,要等構建完後才能得到最終的對象,而模板方法就沒有這麼麻煩,直接使用子類對象便可。
這兩個模式能夠組合使用。
對於複雜的組合結構,可使用生成器模式來一步一步構建。