# 建造者模式(Builder)(生成器)
建造者模式详解
建造者模式详解 2
# 思考
# 与工厂模式有何区别?
- 建造者模式是让建造者类来负责对象的创建工作。上面讲到的工厂模式,是由工厂类来负责对象创建的工作。那它们之间有什么区别呢?
- 实际上,工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化” 地创建不同的对象
网上有一个经典的例子很好地解释了两者的区别:
顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。
# 应用场景
- 需要生成的产品对象有复杂的内部结构,这些产品对象具备共性;
- 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
# 本质
- 用户只需要给出指定复杂对象的类型和内容;
- 建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
# 解决的问题
- 降低创建复杂对象的复杂度
- 隔离了创建对象的构建过程 & 表示
从而:
方便用户创建复杂的对象(不需要知道实现过程)
代码复用性 & 封装性(将对象构建过程和细节进行封装 & 复用)
# 结构
- 生成器的接口定义
/** | |
* 生成器接口,定义创建一个产品对象所需的各个部件的操作 | |
*/ | |
public interface Builder { | |
/** | |
* 示意方法,构建某个部件 | |
*/ | |
public void buildPart(); | |
} |
- 再看看具体的生成器实现,示例代码如下:
/** | |
* 具体的生成器实现对象 | |
*/ | |
public class ConcreteBuilder implements Builder { | |
/** | |
* 生成器最终构建的产品对象 | |
*/ | |
private Product resultProduct; | |
/** | |
* 获取生成器最终构建的产品对象 | |
* @return 生成器最终构建的产品对象 | |
*/ | |
public Product getResult() { | |
return resultProduct; | |
} | |
public void buildPart() { | |
// 构建某个部件的功能处理 | |
} | |
} |
- 看看相应的产品对象的接口示意,示例代码如下:
/** | |
* 被构建的产品对象的接口 | |
*/ | |
public interface Product { | |
// 定义产品的操作 | |
} |
- 再来看看指导者的实现示意,示例代码如下:
/** | |
* 指导者,指导使用生成器的接口来构建产品的对象 | |
*/ | |
public class Director { | |
/** | |
* 持有当前需要使用的生成器对象 | |
*/ | |
private Builder builder; | |
/** | |
* 构造方法,传入生成器对象 | |
* @param builder 生成器对象 | |
*/ | |
public Director(Builder builder) { | |
this.builder = builder; | |
} | |
/** | |
* 示意方法,指导生成器构建最终的产品对象 | |
*/ | |
public void construct() { | |
// 通过使用生成器接口来构建最终的产品对象 | |
builder.buildPart(); | |
} | |
} |
# 实例
先来看看定义的 Builder 接口,主要是把导出各种格式文件的处理过程的步骤定义出来,每个步骤负责构建最终导出文件的一部分。示例代码如下:
/** | |
* 生成器接口,定义创建一个输出文件对象所需的各个部件的操作 | |
*/ | |
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); | |
} |
接下来看看具体的生成器实现,其实就是把原来示例中,写在一起的实现,分拆成多个步骤实现了,先看看导出数据到文本文件的生成器实现,示例代码如下:
/** | |
* 实现导出数据到文本文件的的生成器对象 | |
*/ | |
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; | |
} | |
} |
再看看导出数据到 XML 文件的生成器实现,示例代码如下:
/** | |
* 实现导出数据到 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; | |
} | |
} |
指导者
有了具体的生成器实现后,需要有指导者来指导它进行具体的产品构建,由于构建的产品是文本内容,所以就不用单独定义产品对象了。示例代码如下:
/** | |
* 指导者,指导使用生成器的接口来构建输出的文件的对象 | |
*/ | |
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); | |
} | |
} |
都实现得差不多了,该来写个客户端好好测试一下了。示例代码如下:
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("张三"); | |
// 测试输出到文本文件 | |
TxtBuilder txtBuilder = new TxtBuilder(); | |
// 创建指导者对象 | |
Director director = new Director(txtBuilder); | |
director.construct(ehm, mapData, efm); | |
// 把要输出的内容输出到控制台看看 | |
System.out.println("输出到文本文件的内容:\n"+txtBuilder.getResult()); | |
// 测试输出到 xml 文件 | |
XmlBuilder xmlBuilder = new XmlBuilder(); | |
Director director2 = new Director(xmlBuilder); | |
director2.construct(ehm, mapData, efm); | |
// 把要输出的内容输出到控制台看看 | |
System.out.println("输出到XML文件的内容:\n"+xmlBuilder.getResult()); | |
} | |
} |
# 原型模式 (Prototype)
原型模式 (Prototype)
# 总结
使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。
原型模式主要是拥有一个实例,然后克隆一样的属性,创建出一个新的实例,整理还是非常简单的
# 应用场景
建议在如下情况中,选用原型模式:
如果一个系统想要独立于它想要使用的对象时,可以使用原型模式,让系统只面向接口编程,在系统需要新的对象的时候,可以通过克隆原型来得到;
如果需要实例化的类是在运行时刻动态指定时,可以使用原型模式,通过克隆原型来得到需要的实例;
# 本质
原型模式的本质:克隆生成对象。
克隆是手段,目的还是生成新的对象实例。正是因为原型的目的是为了生成新的对象实例,原型模式通常是被归类为创建型的模式。
原型模式也可以用来解决 “只知接口而不知实现的问题”,使用原型模式,可以出现一种独特的 “接口造接口” 的景象,这在面向接口编程中很有用。同样的功能也可以考虑使用工厂来实现。
另外,原型模式的重心还是在创建新的对象实例,至于创建出来的对象,其属性的值是否一定要和原型对象属性的值完全一样,这个并没有强制规定,只不过在目前大多数实现中,克隆出来的对象和原型对象的属性值是一样的。
也就是说,可以通过克隆来创造值不一样的实例,但是对象类型必须一样。可以有部分甚至是全部的属性的值不一样,可以有选择性的克隆,就当是标准原型模式的一个变形使用吧。
# 和抽象工厂的区别
- 原型模式和抽象工厂模式
功能上有些相似,都是用来获取一个新的对象实例的。
不同之处在于,原型模式的着眼点是在如何创造出实例对象来,最后选择的方案是通过克隆;而抽象工厂模式的着眼点则在于如何来创造产品簇,至于具体如何创建出产品簇中的每个对象实例,抽象工厂模式不是很关注。
正是因为它们的关注点不一样,所以它们也可以配合使用,比如在抽象工厂模式里面,具体创建每一种产品的时候就可以使用该种产品的原型,也就是抽象工厂管产品簇,具体的每种产品怎么创建则可以选择原型模式
- 原型模式和生成器模式
这两种模式可以配合使用。
生成器模式关注的是构建的过程,而在构建的过程中,很可能需要某个部件的实例,那么很自然地就可以应用上原型模式,通过原型模式来得到部件的实例。
# 实例
public interface OrderApi { | |
int getOrderProductNum(); | |
void setOrderProductNum(int num); | |
} |
public class PersonalOrder implements OrderApi, Cloneable { | |
private String customerName; | |
private String productId; | |
private int orderProductNum = 0; | |
public String getCustomerName() { | |
return customerName; | |
} | |
public void setCustomerName(String customerName) { | |
this.customerName = customerName; | |
} | |
@Override | |
public void setOrderProductNum(int orderProductNum) { | |
this.orderProductNum = orderProductNum; | |
} | |
public String getProductId() { | |
return productId; | |
} | |
@Override | |
public String toString() { | |
return "本个人订单的订购人是=" + this.customerName + ",订购产品是=" + this.productId + ",订购数量为=" + this.orderProductNum; | |
} | |
public void setProductId(String productId) { | |
this.productId = productId; | |
} | |
@Override | |
public int getOrderProductNum() { | |
return this.orderProductNum; | |
} | |
@Override | |
// 克隆方法的真正实现,直接调用父类的克隆方法就可以了 | |
protected Object clone() throws CloneNotSupportedException { | |
return super.clone(); | |
} | |
} |
public class main { | |
public static void main(String[] args) throws CloneNotSupportedException { | |
PersonalOrder personalOrder = new PersonalOrder(); | |
personalOrder.setOrderProductNum(100); | |
System.out.println("这是第一次获取的对象实例===" + personalOrder.getOrderProductNum()); | |
PersonalOrder oa2 = (PersonalOrder) personalOrder.clone(); | |
oa2.setOrderProductNum(80); | |
System.out.println("输出克隆出来的实例===" + oa2.getOrderProductNum()); | |
// 再次输出原型实例的值 | |
System.out.println("再次输出原型实例===" + personalOrder.getOrderProductNum()); | |
} | |
} |