摘要
JDK:1.8.0_202
# 一:场景问题
# 1.1 导出文件
在讨论工厂方法模式的时候,提到了一个导出数据的应用框架。
对于导出数据的应用框架,通常在导出数据上,会有一些约定的方式,比如导出成:文本格式、数据库备份形式、Excel格式、Xml格式等等。
在工厂方法模式章节里面,讨论并使用工厂方法模式来解决了如何选择具体导出方式的问题,并没有涉及到每种方式具体如何实现。换句话说,在讨论工厂方法模式的时候,并没有讨论如何实现导出成文本、Xml等具体的格式。
对于导出数据的应用框架,通常对于具体的导出内容和格式是有要求的,假如现在有如下的要求,简单描述一下:
导出的文件,不管什么格式,都分成三个部分,分别是文件头、文件体和文件尾
在文件头部分,需要描述如下信息:分公司或门市点编号、导出数据的日期,对于文本格式,中间用逗号分隔
在文件体部分,需要描述如下信息:表名称、然后分条描述数据。对于文本格式,表名称单独占一行,数据描述一行算一条数据,字段间用逗号分隔
在文件尾部分,需要描述如下信息:输出人
# 1.2 不用模式的解决方案
其实不管什么格式,需要导出的数据是一样的,只是具体导出到文件中的内容,会随着格式的不同而不同
描述输出到文件头的内容的对象:(ExportHeaderModel.java)
/**
* 描述输出到文件头的内容对象
*/
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
描述输出数据的对象:(ExportDataModel.java)
/**
* 描述输出数据的对象
*/
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
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
描述输出文件尾内容的对象:(ExportFooterModel.java)
/**
* 描述输出到文件尾的内容的对象
*/
public class ExportFooterModel {
/**
* 输出人
*/
private String exportUser;
public String getExportUser() {
return exportUser;
}
public void setExportUser(String exportUser) {
this.exportUser = exportUser;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
导出数据到文本文件:(ExportToTxt.java)
/**
* 导出数据到文本文件的对象
*/
public class ExportToTxt {
/**
* 导出数据到文本文件
*
* @param ehm 文件头的内容
* @param mapData 数据的内容
* @param efm 文件尾的内容
*/
public void export(ExportHeaderModel ehm, Map<String, Collection<ExportDataModel>> mapData, ExportFooterModel efm) {
// 用来记录最终输出的文件内容
StringBuilder builder = new StringBuilder();
// 1:先来拼接文件头的内容
builder.append(ehm.getDepId()).append(",").append(ehm.getExportDate()).append("\n");
// 2:接着来拼接文件体的内容
for (String tblName : mapData.keySet()) {
// 先拼接表名称
builder.append(tblName).append("\n");
// 然后循环拼接具体数据
for (ExportDataModel edm : mapData.get(tblName)) {
builder.append(edm.getProductId()).append(",").append(edm.getPrice()).append(",").append(edm.getAmount()).append("\n");
}
}
// 3:接着来拼接文件尾的内容
builder.append(efm.getExportUser());
// 为了演示简洁性,这里就不去写输出文件的代码了
// 把要输出的内容输出到控制台看看
System.out.println("输出到文本文件的内容:\n" + builder);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
导出数据到XML文件:(ExportToXml.java)
/**
* 导出数据到XML文件的对象
*/
public class ExportToXml {
/**
* 导出数据到XML文件
*
* @param ehm 文件头的内容
* @param mapData 数据的内容
* @param efm 文件尾的内容
*/
public void export(ExportHeaderModel ehm, Map<String, Collection<ExportDataModel>> mapData, ExportFooterModel efm) {
// 用来记录最终输出的文件内容
StringBuilder builder = new StringBuilder();
// 1:先来拼接文件头的内容
builder.append("<?xml version='1.0' encoding='gb2312'?>\n");
builder.append("<Report>\n");
builder.append(" <Header>\n");
builder.append(" <DepId>").append(ehm.getDepId()).append("</DepId>\n");
builder.append(" <ExportDate>").append(ehm.getExportDate()).append("</ExportDate>\n");
builder.append(" </Header>\n");
// 2:接着来拼接文件体的内容
builder.append(" <Body>\n");
for (String tblName : mapData.keySet()) {
// 先拼接表名称
builder.append(" <Datas TableName=\"").append(tblName).append("\">\n");
// 然后循环拼接具体数据
for (ExportDataModel edm : mapData.get(tblName)) {
builder.append(" <Data>\n");
builder.append(" <ProductId>").append(edm.getProductId()).append("</ProductId>\n");
builder.append(" <Price>").append(edm.getPrice()).append("</Price>\n");
builder.append(" <Amount>").append(edm.getAmount()).append("</Amount>\n");
builder.append(" </Data>\n");
}
builder.append(" </Datas>\n");
}
builder.append(" </Body>\n");
// 3:接着来拼接文件尾的内容
builder.append(" <Footer>\n");
builder.append(" <ExportUser>").append(efm.getExportUser()).append("</ExportUser>\n");
builder.append(" </Footer>\n");
builder.append("</Report>\n");
// 为了演示简洁性,这里就不去写输出文件的代码了
// 把要输出的内容输出到控制台看看
System.out.println("输出到XML文件的内容:\n" + builder);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
客户端:(Client1.java)
public class Client1 {
public static void main(String[] args) {
//准备测试数据
ExportHeaderModel ehm = new ExportHeaderModel();
ehm.setDepId("一分公司");
ehm.setExportDate("2022-03-17");
Map<String, Collection<ExportDataModel>> mapData = new HashMap<>();
Collection<ExportDataModel> col = new ArrayList<>();
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);
System.out.println("======================================");
//测试输出到xml文件
ExportToXml toXml = new ExportToXml();
toXml.export(ehm, mapData, efm);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
运行结果:
# 1.3 有何问题
仔细观察上面的实现,会发现,不管是输出成文本文件,还是输出到XML文件,在实现的时候,步骤基本上都是一样的,都大致分成了如下四步:
- 先拼接文件头的内容;
- 然后拼接文件体的内容;
- 再拼接文件尾的内容;
- 最后把拼接好的内容输出出去成为文件;
也就是说,对于不同的输出格式,处理步骤是一样的,但是具体每步的实现是不一样的。按照现在的实现方式,就存在如下的问题:
- 构建每种输出格式的文件内容的时候,都会重复这几个处理步骤,应该提炼出来,形成公共的处理过程;
- 今后可能会有很多不同输出格式的要求,这就需要在处理过程不变的情况下,能方便的切换不同的输出格式的处理;
换句话来说,也就是构建每种格式的数据文件的处理过程,应该和具体的步骤实现分开,这样就能够复用处理过程,而且能很容易的切换不同的输出格式。
可是该如何实现呢?
# 二:解决方法
用来解决上述问题的一个合理的解决方案就是生成器模式
生成器模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
# 2.1 解决思路
仔细分析上面的实现,构建每种格式的数据文件的处理过程,这不就是构建过程吗?而每种格式具体的步骤实现,不就相当于是不同的表示吗?因为不同的步骤实现,决定了最终的表现也就不同。也就是说,上面的问题恰好就是生成器模式要解决的问题。
要实现同样的构建过程可以创建不同的表现,那么一个自然的思路就是先把构建过程独立出来,在生成器模式中把它称为指导者,由它来指导装配过程,但是不负责每步具体的实现。当然,光有指导者是不够的,必须要有能具体实现每步的对象,在生成器模式中称这些实现对象为生成器。
这样一来,指导者就是可以重用的构建过程,而生成器是可以被切换的具体实现。前面的实现中,每种具体的导出文件格式的实现就相当于生成器。
# 2.2 模式结构和说明
生成器模式的结构如图所示:
- Builder:生成器接口,定义创建一个Product对象所需要的各个部件的操作。
- ConcreteBuilder:具体的生成器实现,实现各个部件的创建,并负责组装Product对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法。
- Director:指导者,也被称为导向者,主要用来使用Builder接口,以一个统一的过程来构建所需要的Product对象。
- Product:产品,表示被生成器构建的复杂对象,包含多个部件。
# 2.3 示例代码
产品对象接口:(Product.java)
/**
* 被构建的产品对象的接口
*/
public interface Product {
//定义产品的操作
}
2
3
4
5
6
生成器接口定义:(Builder.java)
/**
* 生成器接口,定义创建一个产品对象所需的各个部件的操作
*/
public interface Builder {
/**
* 示意方法,构建某个部件
*/
void buildPart();
}
2
3
4
5
6
7
8
9
生成器实现:(ConcreteBuilder.java)
/**
* 具体的生成器实现对象
*/
public class ConcreteBuilder implements Builder {
/**
* 生成器最终构建的产品对象
*/
private Product resultProduct;
/**
* 获取生成器最终构建的产品对象
*
* @return 生成器最终构建的产品对象
*/
public Product getResult() {
return resultProduct;
}
public void buildPart() {
//构建某个部件的功能处理
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
指导者:(Director.java)
/**
* 指导者,指导使用生成器的接口来构建产品的对象
*/
public class Director {
/**
* 持有当前需要使用的生成器对象
*/
private Builder builder;
/**
* 构造方法,传入生成器对象
*
* @param builder 生成器对象
*/
public Director(Builder builder) {
this.builder = builder;
}
/**
* 示意方法,指导生成器构建最终的产品对象
*/
public void construct() {
//通过使用生成器接口来构建最终的产品对象
builder.buildPart();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 2.4 重写案例
要使用生成器模式来重写示例,重要的任务就是要把指导者和生成器接口定义出来。指导者就是用来执行那四个步骤的对象,而生成器是用来实现每种格式下,对于每个步骤的具体实现的对象。
按照生成器模式重写示例的结构如图所示:
描述输出到文件头的内容的对象:(ExportHeaderModel.java)
/**
* 描述输出到文件头的内容对象
*/
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
描述输出数据的对象:(ExportDataModel.java)
/**
* 描述输出数据的对象
*/
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
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
描述输出文件尾内容的对象:(ExportFooterModel.java)
/**
* 描述输出到文件尾的内容的对象
*/
public class ExportFooterModel {
/**
* 输出人
*/
private String exportUser;
public String getExportUser() {
return exportUser;
}
public void setExportUser(String exportUser) {
this.exportUser = exportUser;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Builder接口:(Builder.java)
/**
* 生成器接口,定义创建一个输出文件对象所需的各个部件的操作
*/
public interface Builder {
/**
* 构建输出文件的Header部分
*
* @param ehm 文件头的内容
*/
void buildHeader(ExportHeaderModel ehm);
/**
* 构建输出文件的Body部分
*
* @param mapData 要输出的数据的内容
*/
void buildBody(Map<String, Collection<ExportDataModel>> mapData);
/**
* 构建输出文件的Footer部分
*
* @param efm 文件尾的内容
*/
void buildFooter(ExportFooterModel efm);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
文本文件的生成器实现:(TxtBuilder.java)
/**
* 实现导出数据到文本文件的的生成器对象
*/
public class TxtBuilder implements Builder {
/**
* 用来记录构建的文件的内容,相当于产品
*/
private StringBuilder builder = new StringBuilder();
@Override
public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {
for (String tblName : mapData.keySet()) {
//先拼接表名称
builder.append(tblName).append("\n");
//然后循环拼接具体数据
for (ExportDataModel edm : mapData.get(tblName)) {
builder.append(edm.getProductId()).append(",").append(edm.getPrice()).append(",").append(edm.getAmount()).append("\n");
}
}
}
@Override
public void buildFooter(ExportFooterModel efm) {
builder.append(efm.getExportUser());
}
@Override
public void buildHeader(ExportHeaderModel ehm) {
builder.append(ehm.getDepId()).append(",").append(ehm.getExportDate()).append("\n");
}
public StringBuilder getResult() {
return builder;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
XML文件的生成器实现:(XmlBuilder.java)
/**
* 实现导出数据到XML文件的的生成器对象
*/
public class XmlBuilder implements Builder {
/**
* 用来记录构建的文件的内容,相当于产品
*/
private StringBuilder builder = new StringBuilder();
public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {
builder.append(" <Body>\n");
for (String tblName : mapData.keySet()) {
//先拼接表名称
builder.append(" <Datas TableName=\"").append(tblName).append("\">\n");
//然后循环拼接具体数据
for (ExportDataModel edm : mapData.get(tblName)) {
builder.append(" <Data>\n");
builder.append(" <ProductId>").append(edm.getProductId()).append("</ProductId>\n");
builder.append(" <Price>").append(edm.getPrice()).append("</Price>\n");
builder.append(" <Amount>").append(edm.getAmount()).append("</Amount>\n");
builder.append(" </Data>\n");
}
builder.append(" </Datas>\n");
}
builder.append(" </Body>\n");
}
public void buildFooter(ExportFooterModel efm) {
builder.append(" <Footer>\n");
builder.append(" <ExportUser>").append(efm.getExportUser()).append("</ExportUser>\n");
builder.append(" </Footer>\n");
builder.append("</Report>\n");
}
public void buildHeader(ExportHeaderModel ehm) {
builder.append("<?xml version='1.0' encoding='gb2312'?>\n");
builder.append("<Report>\n");
builder.append(" <Header>\n");
builder.append(" <DepId>").append(ehm.getDepId()).append("</DepId>\n");
builder.append(" <ExportDate>").append(ehm.getExportDate()).append("</ExportDate>\n");
builder.append(" </Header>\n");
}
public StringBuilder getResult() {
return builder;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
指导者:(Director.java)
/**
* 指导者,指导使用生成器的接口来构建输出的文件的对象
*/
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);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
客户端:(Client2.java)
public class Client2 {
public static void main(String[] args) {
//准备测试数据
ExportHeaderModel ehm = new ExportHeaderModel();
ehm.setDepId("一分公司");
ehm.setExportDate("2022-03-17");
Map<String, Collection<ExportDataModel>> mapData = new HashMap<>();
Collection<ExportDataModel> col = new ArrayList<>();
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());
System.out.println("================================================");
//测试输出到xml文件
XmlBuilder xmlBuilder = new XmlBuilder();
Director director2 = new Director(xmlBuilder);
director2.construct(ehm, mapData, efm);
//把要输出的内容输出到控制台看看
System.out.println("输出到XML文件的内容:\n" + xmlBuilder.getResult());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
运行结果:
通过上面的讲述,应该能很清晰的看出生成器模式的实现方式和它的优势所在了,那就是对同一个构建过程,只要配置不同的生成器实现,就会生成出不同表现的对象。
# 三:模式讲解
# 3.1 认识生成器模式
1. 生成器模式的功能
生成器模式的主要功能是构建复杂的产品,而且是细化的,分步骤的构建产品,也就是生成器模式重在解决一步一步构造复杂对象的问题。如果光是这么认识生成器模式的功能是不够的。
更为重要的是,这个构建的过程是统一的,固定不变的,变化的部分放到生成器部分了,只要配置不同的生成器,那么同样的构建过程,就能构建出不同的产品表示来。
再直白点说,生成器模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用,具体的构造实现可以很方便的扩展和切换,从而可以灵活的组合来构造出不同的产品对象。
2. 生成器模式的构成
要特别注意,生成器模式分成两个很重要的部分:
一个部分是Builder接口这边,这边是定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如何装配这些部件到产品中去。
另外一个部分是Director这边,Director是知道如何组合来构建产品,也就是说Director负责整体的构建算法,而且通常是分步骤的来执行。
3. 生成器模式的使用
应用生成器模式的时候,可以让客户端创造Director,在Director里面封装整体构建算法,然后让Director去调用Builder,让Builder来封装具体部件的构建功能,这就跟前面的例子一样。
还有一种退化的情况,就是让客户端和Director融合起来,让客户端直接去操作Builder,就好像是指导者自己想要给自己构建产品一样。
# 3.2 优缺点
1. 松散耦合
生成器模式可以用同一个构建算法,构建出表现上完全不同的产品,实现产品构建和产品表现上的分离。生成器模式正是把产品构建的过程独立出来,使它和具体产品的表现松散耦合,从而使得构建算法可以复用,而具体产品表现也可以灵活的、方便的扩展和切换。
2. 可以很容易的改变产品的内部表示
在生成器模式中,由于Builder对象只是提供接口给Director使用,那么具体的部件创建和装配方式是被Builder接口隐藏了的,Director并不知道这些具体的实现细节。这样一来,要想改变产品的内部表示,只需要切换Builder的具体实现即可,不用管Director,因此变得很容易。
3. 更好的复用性
生成器模式很好的实现了构建算法和具体产品实现的分离,这样一来,使得构建产品的算法可以复用。同样的道理,具体产品的实现也可以复用,同一个产品的实现,可以配合不同的构建算法使用。
# 3.2 优缺点
1. 松散耦合
**生成器模式可以用同一个构建算法,构建出表现上完全不同的产品,实现产品构建和产品表现上的分离。**生成器模式正是把产品构建的过程独立出来,使它和具体产品的表现松散耦合,从而使得构建算法可以复用,而具体产品表现也可以灵活的、方便的扩展和切换。
2. 可以很容易的改变产品的内部表示
在生成器模式中,由于Builder对象只是提供接口给Director使用,那么具体的部件创建和装配方式是被Builder接口隐藏了的,Director并不知道这些具体的实现细节。这样一来,要想改变产品的内部表示,只需要切换Builder的具体实现即可,不用管Director,因此变得很容易。
3. 更好的复用性
生成器模式很好的实现了构建算法和具体产品实现的分离,这样一来,使得构建产品的算法可以复用。同样的道理,具体产品的实现也可以复用,同一个产品的实现,可以配合不同的构建算法使用。
# 3.3 思考生成器模式
1. 生成器模式的本质
生成器模式的本质:分离整体构建算法和部件构造。
构建一个复杂的对象,本来就有构建的过程,以及构建过程中具体的实现,生成器模式就是用来分离这两个部分,从而使得程序结构更松散、扩展更容易、复用性更好,同时也会使得代码更清晰,意图更明确。
虽然在生成器模式的整体构建算法中,会一步一步引导Builder来构建对象,但这并不是说生成器就主要是用来实现分步骤构建对象的。生成器模式的重心还是在于分离整体构建算法和部件构造,而分步骤构建对象不过是整体构建算法的一个简单表现,或者说是一个附带产物。
2. 何时选用生成器模式
建议在如下情况中,选用生成器模式:
如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时;
如果同一个构建过程有着不同的表示时
# 3.3 相关模式
1. 生成器模式和工厂方法模式
这两个模式可以组合使用。
生成器模式的Builder实现中,通常需要选择具体的部件实现,一个可行的方案就是实现成为工厂方法,通过工厂方法来获取具体的部件对象,然后再进行部件的装配。
2. 生成器模式和抽象工厂模式
这两个模式既相似又有区别,也可以组合使用
说说区别:抽象工厂模式的主要目的是创建产品簇,这个产品簇里面的单个产品,就相当于是构成一个复杂对象的部件对象,抽象工厂对象创建完成过后就立即返回整个产品簇;而生成器模式的主要目的是按照构造算法,一步一步来构建一个复杂的产品对象,通常要等到整个构建过程结束过后,才会得到最终的产品对象。
事实上,这两个模式是可以组合使用的,在生成器模式的Builder实现中,需要创建各个部件对象,而这些部件对象是有关联的,通常是构成一个复杂对象的部件对象,也就是说,Builder实现中,需要获取构成一个复杂对象的产品簇,那自然就可以使用抽象工厂模式来实现。这样一来,由抽象工厂模式负责了部件对象的创建,Builder实现里面就主要负责产品对象整体的构建了。
3. 生成器模式和模板方法模式
这也是两个非常类似的模式。初看之下,不会觉得这两个模式有什么关联,但是仔细一思考,发现两个模式在功能上很类似。
模板方法模式主要是用来定义算法的骨架,把算法中某些步骤延迟到子类中实现。再想想生成器模式,Director用来定义整体的构建算法,把算法中某些涉及到具体部件对象的创建和装配的功能,委托给具体的Builder来实现。
虽然生成器不是延迟到子类,是委托给Builder,但那只是具体实现方式上的差别,从实质上看两个模式很类似,都是定义一个固定的算法骨架,然后把算法中的某些具体步骤交给其它类来完成,都能实现整体算法步骤和某些具体步骤实现的分离。
当然两个模式也有很大的区别:
- 首先是模式的目的,生成器模式是用来构建复杂对象的,而模板方法是用来定义算法骨架,尤其是一些复杂的业务功能的处理算法的骨架;
- 其次是模式的实现,生成器模式是采用委托的方法,而模板方法是采用的继承的方式;
- 另外从使用的复杂度上,生成器模式需要组合Director和Builder对象,然后才能开始构建,要等构建完后才能获得最终的对象,而模板方法就没有这么麻烦,直接使用子类对象即可。
- 生成器模式和组合模式
这两个模式可以组合使用。
# 四:JDK
- java.lang.StringBuilder (opens new window)
- java.nio.ByteBuffer (opens new window)
- java.lang.StringBuffer (opens new window)
- java.lang.Appendable (opens new window)
- Apache Camel builders (opens new window)