1.情境模式
在討論Factory 方法模式的時候,提到了一個匯出資料的應用程式框架,但是沒有涉及到匯出文本的每種方式具體會如何?。現在我們就來解決這個問題
假設匯出的檔案,無論什麼格式都分為三個部分,檔案頭,檔案尾和檔案體。
檔案頭:分公司或者門市點編號,匯出資料的日期
檔案尾:輸出人
檔案體:表名稱
實現匯出資料到文字檔和XML檔案
2.代碼類比2.1標頭檔內容
package demo07.builder.example1;/** * 描述輸出到檔案頭的內容的對象 */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;}}2.2輸出資料的對象
package demo07.builder.example1;/** * 描述輸出資料的對象 */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;}}2.3檔案尾的內容的對象
package demo07.builder.example1;/** * 描述輸出到檔案尾的內容的對象 */public class ExportFooterModel {/** * 輸出人 */private String exportUser;public String getExportUser() {return exportUser;}public void setExportUser(String exportUser) {this.exportUser = exportUser;}}2.4匯出資料到文字檔的對象
package demo07.builder.example1;import java.util.Collection;import java.util.Map;/** * 匯出資料到文字檔的對象 */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);}}2.5匯出資料到XML檔案的對象
package demo07.builder.example1;import java.util.Collection;import java.util.Map;/** * 匯出資料到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);}}2.6用戶端測試
package demo07.builder.example1;import java.util.ArrayList;import java.util.Collection;import java.util.HashMap;import java.util.Map;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);}}3.問題所在
仔細觀察上面的實現,會發現,無論輸出文字檔還是XML檔案,在實現的時候,步驟基本上都是一樣的,大致分為四步:
1. 拼接標頭檔的內容
2. 拼接檔案體的內容
3. 拼接檔案尾的內容
4. 把拼接好的內容輸出到檔案
這說明什麼呢?說明對於不同的輸出格式,處理步驟基本上是一致的。但是每步的具體實現是不一樣的。
那麼我們應該提煉出公用的處理過程,並且要能方便的快速切換不同的輸出格式。
4.解決方案4.1產生器模式的定義:(又叫建造者模式)
將一個複雜物件的構建與他的表示分離,使得同樣的構建過程可以建立不同的表示。
4.2產生器模式的結構圖
5.產生器模式的範例程式碼5.1被構建的產品對象的介面
package demo07.builder.example2;/** * 被構建的產品對象的介面 */public interface Product {// 定義產品的操作}5.2構建器介面,定義建立一個產品對象所需的各個組件的操作
package demo07.builder.example2;/** * 構建器介面,定義建立一個產品對象所需的各個組件的操作 */public interface Builder {/** * 示意方法,構建組件1 */public void buildPart1();/** * 示意方法,構建組件2 */public void buildPart2();/** * 示意方法,構建組件3 */public void buildPart3();}5.3具體的構建器實現對象
package demo07.builder.example2;/** * 具體的構建器實現對象 */public class ConcreteBuilder implements Builder {/** * 構建器最終構建的產品對象 */private Product resultProduct;/** * 擷取構建器最終構建的產品對象 * * @return 構建器最終構建的產品對象 */public Product getResult() {return resultProduct;}@Overridepublic void buildPart1() {// 有關this.resultProduct添加組件的一些操作}@Overridepublic void buildPart2() {// 有關this.resultProduct添加組件的一些操作}@Overridepublic void buildPart3() {// 有關this.resultProduct添加組件的一些操作}}5.4指導者
package demo07.builder.example2;/** * 指導者,指導使用構建器的介面來構建產品的對象 */public class Director {/** * 持有當前需要使用的構建器對象 */private Builder builder;/** * 構造方法,傳入構建器對象 * * @param builder * 構建器對象 */public Director(Builder builder) {this.builder = builder;}/** * 示意方法,指導構建器構建最終的產品對象 */public void construct() {// 通過使用構建器介面來構建最終的產品對象builder.buildPart1();builder.buildPart2();builder.buildPart3();}}5.5用戶端代碼
package demo07.builder.example2;public class Client {public static void main(String[] args) {// 具體實現的Builder構建器(這個是變化的部分)ConcreteBuilder concreteBuilder = new ConcreteBuilder();// 指導者Director director = new Director(concreteBuilder);// 執行方法director.construct();// 輸出Product結果System.out.println(concreteBuilder.getResult());}}6產生器模式思考6.1我自己的思考
大家如果仔細觀察的話,會發現一個問題,就是為什麼非得要指導者呢?直接在Builder中構建一個組件合并的方法不就行了嗎?開始時我想這樣可以,實際上這樣也是可行的。但是,大家仔細想想,可是如果這樣的話,如果有多個Builder的話,那麼每個Builder的方法不一定一樣,那麼你需要知道每個Builder的詳細功能,是不是有點亂?所以如果有引導者來“指導”的話,當你需要擴充功能時候,那麼你只需要在指導者中,添加一個方法就行了,這樣就更加體現了最少知識原則。舉個例子如下,我現在只要構建組件一,組件二,那麼我在指導者中構建一個方法如下所示,即可,那麼我用戶端調用沒有必要需要知道構建者的具體有多少功能,怎麼樣的構建順序最合適。這樣豈不美哉。說白了,就是Builder是用來設計詳細的組件,指導者是用來指導組件的組裝的。對於固定不變的介面Builder,並且方法很詳細,那麼他的具體實作類別盡量不要增刪方法,所有變動放在Director部分。但是對於不同的Builder,我只需要重寫Builder裡面的方法即可,不會改變Director的部分。這樣有很大的優點,耦合性降低,內聚性增高。分層思想明顯。
/** * 示意方法,指導構建器構建最終的產品對象 */public void constructOther() {// 通過使用構建器介面來構建最終的產品對象builder.buildPart1();builder.buildPart2();}
6.2產生器模式的調用順序圖
6.3產生器模式的重寫情境模式樣本
由於這個產生器模式比較簡單,而情境模式代碼比較繁雜,所以我就不貼這段代碼了,大家可以對照著產生器模式的範例程式碼重寫情境模式,很簡單的,相信仔細思考過的都認為很簡單。
6.4產生器模式的重心
重點在於:
1. 分離構造演算法和具體的構造實現
2. 構造演算法可以重用,方便擴充和切換
3. 分層思想明顯,把通用的和具體實現的分離開來。
4. Builder介面負責構建組件的詳細的構造以及組裝。
5. Director介面負責指導產品的裝配組裝。
6.5產生器模式的本質
本質是:分離整體構建演算法和組件構造