架构师内功心法,注重方法调用顺序的建造者模式详解

一、建造者模式的定义

大家平时都去过肯德基用餐,那里不变的是炸鸡、汉堡、薯条、可乐等,这些都是一直都有的,不变的,而其它组合是经常变化的,从而生成不同的“套餐”罢了。而建造模式(Builder Pattern)是将一个复杂的对象的构建过程与它的表示分离,使得同样的构建过程构建不同的表示。使用建造者模式对于用户而言只需要关注指定需要建造的类型就可以获得对象,而不需要了解建造的过程以及细节。

建造者模式适用于创建对象需要很多步骤,但是步骤的顺序不是固定不变的。先看一下建造者模式的类图:

建造者模式中的四个重要角色:

  • 产品(Product):要创建的产品类对象
  • 抽象建造者(Builder):规范产品对象的各个组成部分的建造
  • 建造者(Concrete Builder):具体化对象的各个组成部分的创建
  • 调用者(Director):负责保证对象各部分完整创建或按某种顺序创建

二、建造者模式的应用场景

建造者模式适用于一个具有较多的零件的复杂产品的创建过程,由于需求的变化,组成这个复杂产品的各个零件经常猛烈变化,但是它们的组合方式却相对稳定。

建造者模式适用于以下几种场景:

  • 相同的方法,不同的执行顺序,产生的结果也不同
  • 多个部件或零件,装配到一个对象中,产生的结果不同
  • 产品类复杂,或者产品类中调用顺序不同产生不同的作用
  • 初始化对象特别复杂,参数多,而且很多参数都有默认值

2.1 建造者模式的基本写法

我们以公司的技术培训为例,一个完整的技术培训需要由发布培训通知、制作培训PPT、组织员工培训、现场(远程)培训、提交培训问卷等步骤。下面我们用建造模式的代码来简单实现这类场景,首先创建一个技术培训的 TechnicalTraining 产品类 :

1
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
49
50
public class TechnicalTraining {

private String notice;
private String ppt;
private String training;
private String questionnaire;

public String getNotice() {
return notice;
}

public void setNotice(String notice) {
this.notice = notice;
}

public String getPpt() {
return ppt;
}

public void setPpt(String ppt) {
this.ppt = ppt;
}

public String getTraining() {
return training;
}

public void setTraining(String training) {
this.training = training;
}

public String getQuestionnaire() {
return questionnaire;
}

public void setQuestionnaire(String questionnaire) {
this.questionnaire = questionnaire;
}

@Override
public String toString() {
return "TechnicalTraining{" +
"notice='" + notice + '\'' +
", ppt='" + ppt + '\'' +
", training='" + training + '\'' +
", questionnaire='" + questionnaire + '\'' +
'}';
}

}

接着创建建造者 TrainingBuilder 类,将复杂的构造过程封装起来:

1
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
public class TrainingBuilder {

private TechnicalTraining technicalTraining = new TechnicalTraining();

/**
* 发布培训通知
* @param notice
*/
public void PostNotice(String notice) {
technicalTraining.setNotice(notice);
}

/**
* 制作培训PPT
*/
public void createPPT(String ppt) {
technicalTraining.setPpt(ppt);
}

/**
* 组织员工培训
*/
public void organizeTraining(String training) {
technicalTraining.setTraining(training);
}

/**
* 提交培训问卷
*/
public void sumitQuestionnaire(String questionnaire) {
technicalTraining.setQuestionnaire(questionnaire);
}

public TechnicalTraining build() {
return technicalTraining;
}

}

测试main方法:

public static void main(String[] args) {

TrainingBuilder builder = new TrainingBuilder();
builder.PostNotice("发布培训通知");
builder.createPPT("创建ppt");
builder.organizeTraining("组织员工培训");
builder.sumitQuestionnaire("提交培训问卷");
System.out.println(builder.build());

}

最后来看一下类图:

2.2 建造者模式的链式写法

在平时的应用中,建造者模式通常是采用链式编程的方式构造对象,下面我们来改造上面的案例代码,将TechnicalTraining变成TrainingBuilder的内部类,将构造步骤添加进去,每完成一个步骤,都返回 this:

1
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class TrainingBuilder {

public class TechnicalTraining {

private String notice;
private String ppt;
private String training;
private String questionnaire;

public String getNotice() {
return notice;
}

public void setNotice(String notice) {
this.notice = notice;
}

public String getPpt() {
return ppt;
}

public void setPpt(String ppt) {
this.ppt = ppt;
}

public String getTraining() {
return training;
}

public void setTraining(String training) {
this.training = training;
}

public String getQuestionnaire() {
return questionnaire;
}

public void setQuestionnaire(String questionnaire) {
this.questionnaire = questionnaire;
}
}

private TechnicalTraining technicalTraining = new TechnicalTraining();

/**
* 发布培训通知
* @param notice
*/
public TrainingBuilder PostNotice(String notice) {
technicalTraining.setNotice(notice);
return this;
}

/**
* 制作培训PPT
*/
public TrainingBuilder createPPT(String ppt) {
technicalTraining.setPpt(ppt);
return this;
}

/**
* 组织员工培训
*/
public TrainingBuilder organizeTraining(String training) {
technicalTraining.setTraining(training);
return this;
}

/**
* 提交培训问卷
*/
public TrainingBuilder sumitQuestionnaire(String questionnaire) {
technicalTraining.setQuestionnaire(questionnaire);
return this;
}

public TechnicalTraining build() {
return this.technicalTraining;
}

}

测试main方法:

1
2
3
4
5
6
7
8
9
 public static void main(String[] args) {

TrainingBuilder builder = new TrainingBuilder();
builder.PostNotice("发布培训通知")
.createPPT("创建ppt")
.organizeTraining("组织员工培训")
.sumitQuestionnaire("提交培训问卷");
System.out.println(builder.build());
}

最后再来看下类图:

三、建造者模式在源码中的体现

3.1 StringBuilder类

使用StringBuilder类,我们常用的有append()、toString()方法,我们来看下append()方法的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}

@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}

@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}

3.2 Spring中的BeanDefinitionBuilder类

比如 BeanDefinitionBuilder 通过调用 getBeanDefinition()方法获得一个 BeanDefinition 对象,比如下面的源码:

1
2
3
4
5
6
7
8
9
10
11
12
private BeanDefinitionBuilder(AbstractBeanDefinition beanDefinition) {
this.beanDefinition = beanDefinition;
}

public AbstractBeanDefinition getRawBeanDefinition() {
return this.beanDefinition;
}

public AbstractBeanDefinition getBeanDefinition() {
this.beanDefinition.validate();
return this.beanDefinition;
}

四、建造者模式的优缺点

建造者模式的优点:

  • 封装性好,创建和使用分离;

  • 扩展性好,建造类之间独立、一定程度上解耦。

建造者模式的缺点:

  • 产生多余的 Builder 对象;

  • 产品内部发生变化,建造者都要修改,成本较大。

建造者模式和工厂模式的区别

通过前面的学习,我们已经了解建造者模式,那么它和工厂模式有什么区别呢?
1、建造者模式更加注重方法的调用顺序,工厂模式注重于创建对象。

2、创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样。

3、关注重点不一样,工厂模式模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建出这个对象,还要知道这个对象由哪些部件组成。

4、建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。