JAVA设计模式 — 生成器模式(Builder)
定義:將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
類型:對象創建型模式
類圖:
- Builder:生成器接口,定義創建一個Product對象所需要的各個部件的操作。
- ConcreteBuilder:具體的生成器實現,實現各個部件的創建,并負責組裝Product對象的各個部件,同時還提供一個讓用戶獲取組裝完成后的產品對象的方法。
- Director:指導者,也被稱導向者,主要用來使用Builder接口,以一個統一的過程來構建所需要的Product對象。
- Product:產品,表示被生成器構建的復雜對象,包含多個部件。
生成器模式示例代碼
1、生成器接口定義的示例代碼
/*** 生成器接口,定義創建一個產品對象所需的各個部件的操作* @author FX_SKY**/ public interface Builder {/*** 示意方法,構建某個部件*/public void buildPart(); }
2、具體生成器實現的示例代碼
/*** 具體的生成器實現對象* @author FX_SKY**/ public class ConcreteBuilder implements Builder {private Product resultProduct;/*** 獲取生成器最終構建的產品對象* @return*/public Product getResultProduct() {return resultProduct;}@Overridepublic void buildPart() {//構建某個部件的功能處理}}
3、相應的產品對象接口的示例代碼
/*** 被構建的產品對象的接口* @author FX_SKY**/ public interface Product {//定義產品的操作 }
4、最后是指導者的實現示意,示例代碼如下:
/*** 指導者,指導使用生成器的接口來構建產品對象* @author FX_SKY**/ public class Director {/*** 持有當前需要使用的生成器對象*/private Builder builder;/*** 構造方法,傳人生成器對象* @param builder*/public Director(Builder builder) {this.builder = builder;}/*** 示意方法,指導生成器構建最終的產品對象*/public void construct(){//通過使用生成器接口來構建最終的產品對象builder.buildPart();} }
應用場景-- 導出數據的應用框架
在討論工廠方法模式的時候,提供了一個導出數據的應用框架。
對于導出數據的應用框架,通常在導出數據上,會有一些約束的方式,比如導出成文本格式、數據庫備份形式、Excel格式、Xml格式等。
在工廠方法模式章節里面,討論并使用工廠方法模式來解決了如何選擇具體導出方式的問題,并沒有涉及到每種方式具體如何實現。
換句話說,在討論工廠方法模式的時候,并沒有討論如何實現導出成文本、Xml等具體格式,本章就來討論這個問題。
對于導出數據的應用框架,通常對于具體的導出內容和格式是有要求的,加入現在有如下要求,簡單描述一下:
- 導出的文件,不管是什么格式,都分成3個部分,分別是文件頭、文件體、文件尾。
- 在文件頭部分,需要描述如下信息:分公司或者門市編號、導出數據的日期。
- 在文件體部分,需要描述如下信息:表名稱,然后分條描述數據。
- 在文件尾部分,需要描述如下信息:輸出人。
1、下面將描述文件各個部分的數據對象定義出來
描述輸出到文件頭的內容的對象,示例代碼如下:
/*** 描述輸出到文件頭的內容的對象* @author FX_SKY**/ 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;}}
描述輸出數據的對象,示例代碼如下:
/*** 描述輸出數據的對象* @author FX_SKY**/ 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;}}
描述輸出到文件尾的內容的對象,示例代碼如下:
/*** 描述輸出到文件尾的內容的對象* @author FX_SKY**/ public class ExportFooterModel {/*** 輸出人*/private String exportUser;public String getExportUser() {return exportUser;}public void setExportUser(String exportUser) {this.exportUser = exportUser;}}
2、定義Builder接口,主要是把導出各種格式文件的處理過程的步驟定義出來,每個步驟負責構建最終導出文件的一部分。示例代碼如下:
/*** 生成器接口,定義創建一個輸出文件對象所需的各個部件的操作* @author FX_SKY**/ public interface Builder {/*** 構建輸出文件的Header部分* @param ehm*/public void buildHeader(ExportHeaderModel ehm);/*** 構建輸出文件的Body部分* @param mapData*/public void buildBody(Map<String,List<ExportDataModel>> mapData);/*** 構建輸出文件的Footer部分* @param efm*/public void buildFooter(ExportFooterModel efm); }
3、具體的生成器實現。
導出到文本文件的的生成器實現。示例代碼如下:
/*** 實現導出文件到文本文件的生成器對象* @author FX_SKY**/ public class TxtBuilder implements Builder {/*** 用來記錄構建的文件的內容,相當于產品*/private StringBuffer buffer = new StringBuffer();@Overridepublic void buildHeader(ExportHeaderModel ehm) {buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n");}@Overridepublic void buildBody(Map<String, List<ExportDataModel>> mapData) {for(String tablName : mapData.keySet()){//先拼接表名buffer.append(tablName+"\n");//然后循環拼接具體數據for(ExportDataModel edm : mapData.get(tablName)){buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n");}}}@Overridepublic void buildFooter(ExportFooterModel efm) {buffer.append(efm.getExportUser());}public StringBuffer getResult(){return buffer;}}
導出到Xml文件的的生成器實現。示例代碼如下:
/*** 實現導出文件到Xml文件的生成器對象* @author FX_SKY**/ public class XmlBuilder implements Builder {/*** 用來記錄構建的文件的內容,相當于產品*/private StringBuffer buffer = new StringBuffer();@Overridepublic void buildHeader(ExportHeaderModel ehm) {buffer.append("<?xml version='1.0' encoding='UTF-8'?>\n");buffer.append("<Report>\n");buffer.append("\t<Header>\n");buffer.append("\t\t<DepId>"+ehm.getDepId()+"</DepId>\n");buffer.append("\t\t<ExportDate>"+ehm.getExportDate()+"</ExportDate>\n");buffer.append("\t</Header>\n");}@Overridepublic void buildBody(Map<String, List<ExportDataModel>> mapData) {buffer.append("\t<Body>\n");for(String tablName : mapData.keySet()){//先拼接表名buffer.append("\t\t<Datas TableName=\""+tablName+"\">\n");//然后循環拼接具體數據for(ExportDataModel edm : mapData.get(tablName)){buffer.append("\t\t\t<Data>\n");buffer.append("\t\t\t\t<ProductId>"+edm.getProductId()+"</ProductId>\n");buffer.append("\t\t\t\t<Price>"+edm.getPrice()+"</Price>\n");buffer.append("\t\t\t\t<Amount>"+edm.getAmount()+"</Amount>\n");buffer.append("\t\t\t</Data>\n");}buffer.append("\t\t</Datas>\n");}buffer.append("\t</Body>\n");}@Overridepublic void buildFooter(ExportFooterModel efm) {buffer.append("\t<Footer>\n");buffer.append("\t\t<ExportUser>"+efm.getExportUser()+"</ExportUser>\n");buffer.append("\t</Footer>\n");buffer.append("</Report>\n");}public StringBuffer getResult(){return buffer;}}
4、指導者。有了具體的生成器實現后,需要由指導者來指導它進行具體的產品構建。示例代碼如下:
/*** 指導者,指導使用生成器的接口來構建輸出的文件對象* * @author FX_SKY* */ public class Director {/*** 持有當前需要的使用的生成器對象*/private Builder builder;/*** 構造方法,傳入生成器對象* * @param builder*/public Director(Builder builder) {this.builder = builder;}public void construct(ExportHeaderModel ehm,Map<String, List<ExportDataModel>> mapData, ExportFooterModel efm) {//1.先構建Headerbuilder.buildHeader(ehm);//2.然后構建Bodybuilder.buildBody(mapData);//3.再構建Footerbuilder.buildFooter(efm);} }
5、客戶端測試代碼如下:
public class Client {/*** @param args*/public static void main(String[] args) {//準備測試數據ExportHeaderModel ehm = new ExportHeaderModel();ehm.setDepId("一分公司");ehm.setExportDate("2010-05-18");Map<String, List<ExportDataModel>> mapData = new HashMap<String, List<ExportDataModel>>();List<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(120);edm2.setAmount(280);ExportDataModel edm3 = new ExportDataModel();edm3.setProductId("產品003號");edm3.setPrice(320);edm3.setAmount(380);col.add(edm1);col.add(edm2);col.add(edm3);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("輸出到文本文件的內容:"+txtBuilder.getResult().toString());XmlBuilder xmlBuilder = new XmlBuilder();Director director2 = new Director(xmlBuilder);director2.construct(ehm, mapData, efm);//把要輸出的內容輸出到控制臺看看System.out.println("輸出到Xml文件的內容:"+xmlBuilder.getResult().toString());}}
生成器模式的功能
生成器模式的主要功能是構建復雜的產品,而且是細化的,分步驟的構建產品,也就是生成器模式重在一步一步解決構造復雜對象的問題。如果僅僅這么認知生成器模式的功能是不夠的。
更為重要的是,這個構建的過程是統一的、固定不變的,變化的部分放到生成器部分了,只要配置不同的生成器,那么同樣的構建過程,就能構建出不同的產品來。
使用生成器模式構建復雜的對象
考慮這樣的一個實際應用,Android圖片異步加載框架,需要要創建圖片加載配置的對象,里面很多屬性的值都有約束,要求創建出來的對象是滿足這些約束規則的。約束規則比如,線程池的數量不能小于2個、內存圖片緩存的大小不能為負值等等。
要想簡潔直觀、安全性好,有具有很好的擴展性地創建這個對象的話,一個較好的選擇就是使用Builder模式,把復雜的創建過程通過Builder來實現。
采用Builder模式來構建復雜的對象,通常會對Builder模式進行一定的簡化,因為目標明確,就是創建某個復雜對象,因此做適當簡化會使程序更簡潔。大致簡化如下:
- 由于是用Builder模式來創建某個對象,因此就沒有必要再定義一個Builder接口,直接提供一個具體的構建器類就可以了。
- 對于創建一個負責的對象,可能會有很多種不同的選擇和步驟,干脆去掉“指導者”,把指導者的功能和Client的功能合并起來,也就是說,Client就相當于指導者,它來指導構建器類去構建需要的復雜對象。
public final class ImageLoaderConfiguration {final Executor taskExecutor;final int memoryCacheSize;final int threadPoolSize;final int threadPriority;final boolean writeLogs;private ImageLoaderConfiguration(final Builder builder) {taskExecutor = builder.taskExecutor;threadPoolSize = builder.threadPoolSize;threadPriority = builder.threadPriority;memoryCacheSize = builder.memoryCacheSize;writeLogs = builder.writeLogs;}/*** Builder for {@link ImageLoaderConfiguration}** @author Sergey Tarasevich (nostra13[at]gmail[dot]com)*/public static class Builder {public static final int DEFAULT_THREAD_POOL_SIZE = 3;public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 1;private int memoryCacheSize = 0;private Executor taskExecutor = null;private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;private int threadPriority = DEFAULT_THREAD_PRIORITY;private boolean writeLogs = false;public Builder() {}public Builder taskExecutor(Executor executor) {if (threadPoolSize != DEFAULT_THREAD_POOL_SIZE || threadPriority != DEFAULT_THREAD_PRIORITY) {}this.taskExecutor = executor;return this;}public Builder threadPoolSize(int threadPoolSize) {this.threadPoolSize = threadPoolSize;return this;}public Builder threadPriority(int threadPriority) {if (threadPriority < Thread.MIN_PRIORITY) {this.threadPriority = Thread.MIN_PRIORITY;} else {if (threadPriority > Thread.MAX_PRIORITY) {this.threadPriority = Thread.MAX_PRIORITY;} else {this.threadPriority = threadPriority;}}return this;}public Builder memoryCacheSize(int memoryCacheSize) {if (memoryCacheSize <= 0) throw new IllegalArgumentException("memoryCacheSize must be a positive number");this.memoryCacheSize = memoryCacheSize;return this;}public Builder writeDebugLogs() {this.writeLogs = true;return this;}/** Builds configured {@link ImageLoaderConfiguration} object */public ImageLoaderConfiguration build() {initEmptyFieldsWithDefaultValues();return new ImageLoaderConfiguration(this);}private void initEmptyFieldsWithDefaultValues() {if (taskExecutor == null) {}}} } 客戶端調用示例代碼如下:
public class Client {/*** @param args*/public static void main(String[] args) {ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder().taskExecutor(Executors.newCachedThreadPool()).threadPoolSize(3).threadPriority(Thread.MIN_PRIORITY + 3).memoryCacheSize(1024*16).build();}}
生成器模式的優點
松散耦合
生成器模式可以用同一個構建算法構建出表現上完全不同的產品,實現產品構建和產品表現上的分離。生成器模式正是把產品構建的過程獨立出來,使它和具體產品的表現分松散耦合,從而使得構建算法可以復用,而具體產品表現也可以很靈活地、方便地擴展和切換。
可以很容易的改變產品的內部表示
在生成器模式中,由于Builder對象只是提供接口給Director使用,那么具體部件創建和裝配方式是被Builder接口隱藏了的,Director并不知道這些具體的實現細節。這樣一來,要想改變產品的內部表示,只需要切換Builder接口的具體實現即可,不用管Director,因此變得很容易。
更好的復用性
生成器模式很好的實現構建算法和具體產品實現的分離。這樣一來,使得構建產品的算法可以復用。同樣的道理,具體產品的實現也可以復用,同一個產品的實現,可以配合不同的構建算法使用。
生成器模式的本質:分離整體構建算法和部件構造。
雖然在生成器模式的整體構建算法中,會一步一步引導Builder來構建對象,但這并不是說生成器主要就是用來實現分步驟構建對象的。生成器模式的重心還是在于分離整體構建算法和部件構造,而分步驟構建對象不過是整體構建算法的一個簡單表現,或者說是一個附帶產物。
何時選用生成器模式
建議在以下情況中選用生成器模式。
- 如果創建對象的算法,應該獨立于該對象的組成部分以及它們的裝配方式時。
- 如果同一個構建過程有著不同的表示時。
總結
以上是生活随笔為你收集整理的JAVA设计模式 — 生成器模式(Builder)的全部內容,希望文章能夠幫你解決所遇到的問題。