1.场景模式
在讨论工厂方法模式的时候,提到了一个导出数据的应用框架,但是没有涉及到导出文本的每种方式具体会如何实现。现在我们就来解决这个问题
假设导出的文件,无论什么格式都分为三个部分,文件头,文件尾和文件体。
文件头:分公司或者门市点编号,导出数据的日期
文件尾:输出人
文件体:表名称
实现导出数据到文本文件和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; } @Override public void buildPart1() { // 有关this.resultProduct添加组件的一些操作 } @Override public void buildPart2() { // 有关this.resultProduct添加组件的一些操作 } @Override public 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生成器模式的本质
本质是:分离整体构建算法和部件构造