参考链接:建造者(Builder)模式

1. 设计意图及特点

建造者(Builder)模式的定义:将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。

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

建造者模式的意图是为了构造对象,因此它属于创建型模式。

例如对一个迷宫可能有墙、房间和门,并且数量不计。迷宫可能仅由一堵墙构成,也可能由两堵墙构成,也可能由两个房间加一扇门构成…如果采用重载的方式生产迷宫,代码量是难以计数的、无比庞大的。

针对一个对象拥有大量的组件(迷宫可以拥有很多墙或房间或门),而构造这个对象对其组件的使用又是不确定的这一问题(使用墙、房间、门的数量是不确定的),想要精细的控制构建过程,此时可以采用建造者模式解决问题。

优点

  • 封装性良好,构建和表示分离。
  • 扩展性比较好,各个具体的建造者相互独立,有利于系统的解耦。
  • 客户端没必要知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。

缺点

  • 产品的组成部分必须相同,这限制了其使用范围。
  • 如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
  • 为了创建对象,必须先创建它的构建器,在十分注重性能的情况下,这是一笔性能开销。

注意

建造者模式和工厂模式的关注点不同: 建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。

2. 适用场景

  • 构造过程中,被构造的对象具有不同的表示。
  • 一些基本部件不会变,而其组合经常变化的时候。
  • 需要生成的对象内部属性本身相互依赖。

3. 经典建造者模式

建造者模式分为两种,一种为经典建造者模式,另一种为变种建造者模式。

我们日常开发中经典建造者模式一般不常用,用的比较多的是变种的建造者模式。所以我们应该重点关注变种的建造者模式。

3.1 模式的结构

建造者(Builder)模式的主要角色如下:

  • 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。

  • 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()

  • 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。

  • 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。

建造者模式的结构图:

建造者模式结构图

3.2 代码实现

首先我们先来创建一个Product类:

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
public class Product {
private String partA;
private String partB;
private String partC;

public Product( ) {
}

public Product(String partA, String partB, String partC) {
this.partA = partA;
this.partB = partB;
this.partC = partC;
}

public void setPartA(String partA) {
this.partA = partA;
}

public void setPartB(String partB) {
this.partB = partB;
}

public void setPartC(String partC) {
this.partC = partC;
}

public String getPartA() {
return partA;
}

public String getPartB() {
return partB;
}

public String getPartC() {
return partC;
}

public void show() {
System.out.println("产品的部件A=" + partA);
System.out.println("产品的部件B=" + partB);
System.out.println("产品的部件C=" + partC);
}

}

创建一个抽象的生产产品过程的Builder类:

1
2
3
4
5
6
7
8
9
10
11
12
// 抽象的构造者
public abstract class ProductBuilder {
// 抽象的构造方法
public abstract void builderPartA();

public abstract void builderPartB();

public abstract void builderPartC();

// 抽象地获取产品的方法
public abstract Product getResult();
}

实体建造者类,可以根据需求构建出多个实体建造者类,在此只构建一个 ConcreteBuilder1 类:

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
public class ConcreteBuilder1 extends ProductBuilder {

private Product product;

public ConcreteBuilder1() {
this.product = new Product();
}

@Override
public void builderPartA() {
product.setPartA("partA");
}

@Override
public void builderPartB() {
product.setPartB("partB");
}

@Override
public void builderPartC() {
product.setPartC("partC");
}

@Override
public Product getResult() {
return product;
}
}

指导者类:

1
2
3
4
5
6
7
8
9
10
11
12
public class ProductDirect {

public void Director(ProductBuilder builder) {
/*
* 要为目标实体设置那些可选参数就在此设置
*/
builder.builderPartA();
builder.builderPartB();
builder.builderPartC();
}

}

测试:

1
2
3
4
5
6
7
8
@Test
public void testProduct() {
ProductDirect direct = new ProductDirect();
ConcreteBuilder1 builder = new ConcreteBuilder1();
direct.Director(builder);
Product product = builder.getResult();
product.show();
}

测试结果:

1
2
3
产品的部件A=partA
产品的部件B=partB
产品的部件C=partC

4. 变种建造者模式

4.1 如何使用

以 Person 为例:

  • 在 Person 中创建一个静态嵌套类 Builder,并将 Person 中的参数都复制到 Builder 类中。

  • 在 Person 中提供一个 非 public 的构造函数,参数类型为 Builder。

  • 在 Builder 中提供一个 public 的构造函数,参数为构造 Person 对象时的必填参数。

  • 在 Builder 中提供多个设置方法,用于对 Person 中可选参数进行赋值,返回值为 Builder 类型的实例(用于链式调用)。

  • 在 Builder 中提供一个 build() 方法(当然叫啥名都可以,作为构建方法),用于构建最终的 Person 实例并返回。

4.2 代码实现

新建一个Person 类:

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
public class Person {
/*名字(必须)*/
private String name;
/*性别(必须)*/
private String gender;
/*年龄(非必须)*/
private String age;
/*职业(非必须)*/
private String career;

private Person(Builder builder) {
this.name = builder.name;
this.gender = builder.gender;
this.age = builder.age;
this.career = builder.career;
}

public String getName() {
return name;
}

public String getGender() {
return gender;
}

public String getAge() {
return age;
}

public String getCareer() {
return career;
}

public static class Builder {
private String name;
private String gender;
private String age;
private String career;

public Builder(String name, String gender) {
this.name = name;
this.gender = gender;
}

public Builder age(String age) {
this.age = age;
return this;
}

public Builder career(String career) {
this.career = career;
return this;
}

public Person build() {
return new Person(this);
}
}

}

测试:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testBuilder() {
Person person = new Person.Builder("木又枯了", "男")
.age("18")
.career("小学生")
.build();
System.out.println("Name = "+ person.getName());
System.out.println("Gender = "+ person.getGender());
System.out.println("Age = "+ person.getAge());
System.out.println("Career = "+ person.getCareer());
}

测试结果:

1
2
3
4
Name = 木又枯了
Gender = 男
Age = 18
Career = 小学生

说明

相比于经典建造者模式,变种建造者模式省略了 Director 这个角色,将构建算法交给了 Client 端,其次将 Builder 写到了要构建的产品类里面,最后采用了链式调用。

上述实现中的 Person 对象的属性值都是无法修改的,因此建造者模式也常用于创建 一个成员变量不可变的对象。所以在最初的建造者模式中,常把 Person 的属性设置成 final 的,Person类中定义的与其属性相同的内部类Builder中必须具备的属性也需要用 final 修饰,防止这些属性没有被赋值,其他非必须的属性不能用final,因为如果加了final,就必须对其进行初始化,这样这些非必须的属性又变成必须的。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {
/*名字(必须)*/
private final String name;
/*性别(必须)*/
private final String gender;
/*年龄(非必须)*/
private final String age;
/*职业(非必须)*/
private final String career;

// -- skip --

public static class Builder {
private final String name;
private final String gender;
private String age;
private String career;

// -- skip --

}
}