定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
类型:对象创建型模式
类图:
生成器模式示例代码
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; } @Override public 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等具体格式,本章就来讨论这个问题。
对于导出数据的应用框架,通常对于具体的导出内容和格式是有要求的,加入现在有如下要求,简单描述一下:
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; } }
/** * 生成器接口,定义创建一个输出文件对象所需的各个部件的操作 * @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); }
导出到文本文件的的生成器实现。示例代码如下:
/** * 实现导出文件到文本文件的生成器对象 * @author FX_SKY * */ public class TxtBuilder implements Builder { /** * 用来记录构建的文件的内容,相当于产品 */ private StringBuffer buffer = new StringBuffer(); @Override public void buildHeader(ExportHeaderModel ehm) { buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n"); } @Override public 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"); } } } @Override public 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(); @Override public 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"); } @Override public 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"); } @Override public 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.先构建Header builder.buildHeader(ehm); //2.然后构建Body builder.buildBody(mapData); //3.再构建Footer builder.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模式进行一定的简化,因为目标明确,就是创建某个复杂对象,因此做适当简化会使程序更简洁。大致简化如下:
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来构建对象,但这并不是说生成器主要就是用来实现分步骤构建对象的。生成器模式的重心还是在于分离整体构建算法和部件构造,而分步骤构建对象不过是整体构建算法的一个简单表现,或者说是一个附带产物。
何时选用生成器模式
建议在以下情况中选用生成器模式。