大话设计模式十三:生成器模式(builder)

一. 定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。


二. 结构和说明:

大话设计模式十三:生成器模式(builder)_第1张图片

builder:生成器接口,定义创建一个Product对象所需的各个部件的操作

/**
 * 构建器接口,定义创建一个产品对象所需的各个部件的操作
 */
public interface Builder {
	/**
	 * 示意方法,构建某个部件
	 */
	public void buildPart();
}
ConcreteBuilder:具体的生成器实现,实现各个部件的创建,并负责组装Product对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法
public class ConcreteBuilder implements Builder {
	// 构建器最终构建的产品对象
	private Product resultProduct;
	/**
	 * 获取构建器最终构建的产品对象
	 * @return 构建器最终构建的产品对象
	 */
	public Product getResult() {
		return resultProduct;
	}
	public void buildPart() {
		//构建某个部件的功能处理
	}
}
Director:指导者,主要用来使用Builder接口,以一个统一的过程来构建所需要的Product对象
public class Director {
	/**
	 * 持有当前需要使用的构建器对象
	 */
	private Builder builder;
	/**
	 * 构造方法,传入构建器对象
	 * @param builder 构建器对象
	 */
	public Director(Builder builder) {
		this.builder = builder;
	}
	/**
	 * 示意方法,指导构建器构建最终的产品对象
	 */
	public void construct() {
		//通过使用构建器接口来构建最终的产品对象
		builder.buildPart();
	}
}
Product:产品,表示被生成器构建的负责对象,包含多个部件
public interface Product {
	//定义产品的操作
}


三. 生成器模式应用

实现数据导出,导出的文件不管什么格式,都分成三个部分,分别是文件头、文件体和文件尾,可以导出文本格式和xml格式。

不用模式的方式:

1. 描述输出到文件头的对象

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. 描述输出数据的对象
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;
	}
}
3. 描述输出到文件尾的对象
public class ExportFooterModel {
	// 输出人
	private String exportUser;

	public String getExportUser() {
		return exportUser;
	}

	public void setExportUser(String exportUser) {
		this.exportUser = exportUser;
	}
	
}
4. 导出数据到文本对象
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);
	}
}
5. 导出数据到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);
	}
}
6. 测试
public class Client {
	public static void main(String[] args) {
		//准备测试数据
		ExportHeaderModel header = new ExportHeaderModel(); 
		header.setDepId("一分公司");
		header.setExportDate("2012-06-26");
		
		Map<String,Collection<ExportDataModel>> mapData = new HashMap<String,Collection<ExportDataModel>>();
		Collection<ExportDataModel> col = new ArrayList<ExportDataModel>();
		
		ExportDataModel data1 = new ExportDataModel();
		data1.setProductId("产品001号");
		data1.setPrice(100);
		data1.setAmount(80); 
		
		ExportDataModel data2 = new ExportDataModel();
		data2.setProductId("产品002号");
		data2.setPrice(99);
		data2.setAmount(55);		
		//把数据组装起来
		col.add(data1);
		col.add(data2);		
		mapData.put("销售记录表", col); 
		
		ExportFooterModel footer = new ExportFooterModel(); 
		footer.setExportUser("张三");
		
		//测试输出到文本文件
		ExportToTxt toTxt = new ExportToTxt();
		toTxt.export(header, mapData, footer);
		System.out.println("---------------------");
		//测试输出到xml文件
		ExportToXml toXml = new ExportToXml();
		toXml.export(header, mapData, footer);
	}
}

上面的实现方式,在构建每种输出格式的文件内容的时候,都会重复这几个处理步骤,应该提炼出来,形成公共的处理过程。

使用模式的方式:

1. 写一个构建器接口,定义创建一个输出文件对象所需的各个部件的操作

public interface Builder {
	/**
	 * 构建输出文件的Header部分
	 * @param ehm 文件头的内容
	 */
	public void buildHeader(ExportHeaderModel ehm);
	/**
	 * 构建输出文件的Body部分
	 * @param mapData 要输出的数据的内容
	 */
	public void buildBody(Map<String,Collection<ExportDataModel>> mapData);
	/**
	 * 构建输出文件的Footer部分
	 * @param efm 文件尾的内容
	 */
	public void buildFooter(ExportFooterModel efm);
}
2. 实现导出数据到文本文件的构建器对象
public class TxtBuilder implements Builder {
	/**
	 * 用来记录构建的文件的内容,相当于产品
	 */
	private StringBuffer buffer = new StringBuffer();
	public void buildBody(
			Map<String, Collection<ExportDataModel>> mapData) {
		for(String tblName : mapData.keySet()){
			//先拼接表名称
			buffer.append(tblName+"\n");
			//然后循环拼接具体数据
			for(ExportDataModel edm : mapData.get(tblName)){
				buffer.append(edm.getProductId()+","+edm.getPrice()+","+edm.getAmount()+"\n");
			}
		}
	}
	public void buildFooter(ExportFooterModel efm) {
		buffer.append(efm.getExportUser());
	}
	public void buildHeader(ExportHeaderModel ehm) {
		buffer.append(ehm.getDepId()+","+ehm.getExportDate()+"\n");
	}	
	public StringBuffer getResult(){
		return buffer;
	}	
}
3. 实现导出数据到xml文件的构建器对象
public class XmlBuilder implements Builder {
	/**
	 * 用来记录构建的文件的内容,相当于产品
	 */
	private StringBuffer buffer = new StringBuffer();
	public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {
		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");
	}

	public void buildFooter(ExportFooterModel efm) {
		buffer.append("  <Footer>\n");
		buffer.append("    <ExportUser>"+efm.getExportUser()+"</ExportUser>\n");
		buffer.append("  </Footer>\n");
		buffer.append("</Report>\n");
	}

	public void buildHeader(ExportHeaderModel ehm) {
		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");
	}
	public StringBuffer getResult(){
		return buffer;
	}
}
4. 指导者,指导使用构建器的接口来构建输出的文件对象
public class Director {
	/**
	 * 持有当前需要使用的构建器对象
	 */
	private Builder builder;
	/**
	 * 构造方法,传入构建器对象
	 * @param builder 构建器对象
	 */
	public Director(Builder builder) {
		this.builder = builder;
	}
	/**
	 * 指导构建器构建最终的输出的文件的对象
	 * @param ehm 文件头的内容
	 * @param mapData 数据的内容
	 * @param efm 文件尾的内容
	 */
	public void construct(ExportHeaderModel ehm,Map<String,Collection<ExportDataModel>> 
		mapData,ExportFooterModel efm) {
		//进行数据校验
		
		//1:先构建Header
		builder.buildHeader(ehm);
		//2:然后构建Body
		builder.buildBody(mapData);
		//3:然后构建Footer
		builder.buildFooter(efm);
	}
}
5. 测试
public class Client {
	public static void main(String[] args) {
		//准备测试数据
		
		//测试输出到文本文件
		TxtBuilder txtBuilder = new TxtBuilder();
		//创建指导者对象
		Director director = new Director(txtBuilder);
		
		director.construct(header, mapData, footer);
		//把要输出的内容输出到控制台看看
		System.out.println("输出到文本文件的内容:\n"+txtBuilder.getResult());
		
		
		//测试输出到xml文件
		XmlBuilder xmlBuilder = new XmlBuilder();
		
		xmlBuilder.buildHeader(header);
		xmlBuilder.buildBody(mapData);
		xmlBuilder.buildFooter(footer);
		
		//把要输出的内容输出到控制台看看
		System.out.println("输出到XML文件的内容:\n"+xmlBuilder.getResult());
		
	}
}

生成器模式的重心在于分离构建算法和具体的构建实现,从而使得构建算法可以重用,具体的构建实现可以很方便的扩展和切换,从而可以灵活的组合来构建出不同的产品对象。


四. 构建复杂对象

现在要创建一个保险合同的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。

约束规则:保险合同通常情况下可以和个人签订,也可以和某个公司签订,但是一份保险合同不能同时与个人和公司签订。

1. 保险合同对象

public class InsuranceContract {
	// 保险合同编号
	private String contractId;
	
	/**
	 * 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订,
	 * 也就是说,"被保险人员"和"被保险公司"这两个属性,不可能同时有值
	 */
	private String personName;
	
	// 被保险公司的名称
	private String companyName;
	
	// 示例:其它数据
	private String otherData;
	
	// 构造方法,访问级别是同包能访问
	InsuranceContract(ConcreteBuilder builder){
		this.contractId = builder.getContractId();
		this.personName = builder.getPersonName();
		this.companyName = builder.getCompanyName();
		this.otherData = builder.getOtherData();
	}
	
	/**
	 * 示意:保险合同的某些操作
	 */
	public void someOperation(){
		System.out.println("Now in Insurance Contract someOperation=="+this.contractId+", personName="+personName);
	}
}
2. 构建保险合同对象的构建器
public class ConcreteBuilder {
	private String contractId;
	private String personName;
	private String companyName;
	private String otherData;
	
	/**
	 * 构造方法,传入必须要有的参数
	 */
	public ConcreteBuilder(String contractId){
		this.contractId = contractId;
	}
	
	/**
	 * 选填数据,被保险人员的名称
	 */
	public ConcreteBuilder setPersonName(String personName){
		this.personName = personName;
		return this;
	}
	
	/**
	 * 选填数据,被保险公司的名称
	 */
	public ConcreteBuilder setCompanyName(String companyName){
		this.companyName = companyName;
		return this;
	}
	/**
	 * 选填数据,其它数据
	 */
	public ConcreteBuilder setOtherData(String otherData){
		this.otherData = otherData;
		return this;
	}
	/**
	 * 构建真正的对象并返回
	 */
	public InsuranceContract build(){
		if(contractId==null || contractId.trim().length()==0){
			throw new IllegalArgumentException("合同编号不能为空");
		}
		boolean signPerson = personName!=null && personName.trim().length()>0;
		boolean signCompany = companyName!=null && companyName.trim().length()>0;
		if(signPerson && signCompany){
			throw new IllegalArgumentException("一份保险合同不能同时与人和公司签订");
		}		
		if(signPerson==false && signCompany==false){
			throw new IllegalArgumentException("一份保险合同不能没有签订对象");
		}
		return new InsuranceContract(this);
	}
	
	public String getContractId() {
		return contractId;
	}

	public String getPersonName() {
		return personName;
	}

	public String getCompanyName() {
		return companyName;
	}

	public String getOtherData() {
		return otherData;
	}
}
3. 测试:
public class Client {
	public static void main(String[] args) {
		//创建构建器
		ConcreteBuilder builder = new ConcreteBuilder("001");
		
		//设置需要的数据,然后构建保险合同对象
		InsuranceContract contract = builder.setOtherData("test").setCompanyName("cc").build();
		
		//操作保险合同对象的方法
		contract.someOperation();
	}
}
可以把构建器对象和被构建对象合并
public class InsuranceContract {
	// 保险合同编号
	private String contractId;
	
	// 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订, 也就是说,"被保险人员"和"被保险公司"这两个属性,不可能同时有值
	private String personName;
	
	// 被保险公司的名称
	private String companyName;
	
	// 示例:其它数据
	private String otherData;

	// 构造方法,访问级别是私有的
	private InsuranceContract(ConcreteBuilder builder) {
		this.contractId = builder.contractId;
		this.personName = builder.personName;
		this.companyName = builder.companyName;
		this.otherData = builder.otherData;
	}

	/**
	 * 构造保险合同对象的构建器
	 */
	public static class ConcreteBuilder {
		private String contractId;
		private String personName;
		private String companyName;
		private String otherData;

		// 构造方法,传入必须要有的参数
		public ConcreteBuilder(String contractId) {
			this.contractId = contractId;
		}

		// 选填数据,被保险人员的名称
		public ConcreteBuilder setPersonName(String personName) {
			this.personName = personName;
			return this;
		}

		// 选填数据,被保险公司的名称
		public ConcreteBuilder setCompanyName(String companyName) {
			this.companyName = companyName;
			return this;
		}

		// 选填数据,其它数据
		public ConcreteBuilder setOtherData(String otherData) {
			this.otherData = otherData;
			return this;
		}

		/**
		 * 构建真正的对象并返回
		 */
		public InsuranceContract build() {
			if (contractId == null || contractId.trim().length() == 0) {
				throw new IllegalArgumentException("合同编号不能为空");
			}
			boolean signPerson = personName != null && personName.trim().length() > 0;
			boolean signCompany = companyName != null && companyName.trim().length() > 0;
			if (signPerson && signCompany) {
				throw new IllegalArgumentException("一份保险合同不能同时与人和公司签订");
			}
			if (signPerson == false && signCompany == false) {
				throw new IllegalArgumentException("一份保险合同不能没有签订对象");
			}

			return new InsuranceContract(this);
		}
	}

	/**
	 * 示意:保险合同的某些操作
	 */
	public void someOperation() {
		System.out.println("Now in Insurance Contract someOperation: " + this.contractId);
	}
}
测试:
public class Client {
	public static void main(String[] args) {
		// 创建构建器
		InsuranceContract.ConcreteBuilder builder = new InsuranceContract.ConcreteBuilder("001");
		
		// 设置需要的数据,然后构建保险合同对象
		InsuranceContract contract = builder.setPersonName("张三").setOtherData("test").build();
		
		// 操作保险合同对象的方法
		contract.someOperation();
	}
}

四. 生成器模式特点

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

何时选用?

1. 如果创建对象的算法应该独立于该对象的组成部分以及它们的装配方式时。

2. 如果同一个构建过程有着不同的表示时。





你可能感兴趣的:(设计模式,builder,建造者,构建者)