现在的位置: 首页 > 综合 > 正文

设计模式09—设计模式之生成器模式(Builder)也叫建造者模式(创建型)

2013年08月14日 ⁄ 综合 ⁄ 共 6950字 ⁄ 字号 评论关闭
文章目录

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生成器模式的本质

本质是:分离整体构建算法和部件构造

抱歉!评论已关闭.