0%

构建者模式

定义

构建者模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。这个定义看起来文绉绉的不好理解。其实就是在创建对象时可以“按需”使用对象的的某个(某几个)属性,并在必要时对这些属性进行校验。

使用场景

构建者模式是用在创建对象的时候的,在创建对象的时候同时指定一个或多个属性值。这看起来是可以通过构造函数来实现的。比如对于对于 Student 类,可以通过以下的方式来实现:将类中的属性进行组合,然后依次写上构造函数。不过这对属性少的对象来说还可以,对属性多的对象,组合起来就很多了,而且以后增加一个属性又得进行大改动,同时还存在一个问题,就是属性的类型可能相同,导致部分组合无法实现。另外,对于属性个数多的构造函数,调用者也不太容易区分属性的个数,也不容易扩展,因此这种方式不太好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student {
private int id;

private String name;

public Student(int id, String name) {
this.id = id;
this.name = name;
}

public Student(int id) {
this.id = id;
}

public Student(String name) {
this.name = name;
}
}

使用构造函数不满足要求,此时,我们很自然的就想到了可以是用 set 方法进行属性的设置。比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student {
private int id;

private String name;

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}
}

// 调用的地方
Student student = new Student();
student.setId(1);
student.setName("Alice")

这种方式可以让调用者自行决定设置某些值,也解决了构造函数过多组合的问题,但是这种方式存在其他的问题:

  1. 不能对属性值进行校验,即:满足某种条件时允许创建对象,不满足时不允许创建
  2. 可能存在无效状态的问题。

先说第一个问题:不能对属性值进行校验。使用 set 的方式看起来是可以对属性值进行校验的,比如对于 setId 这个函数,我可以在函数内部判断,如果 id 范围小于 0,则抛异常,如下所示

1
2
3
4
5
6
7
public void setId(int id) {
if (id < 0) {
throw new IllegalArgumentException("id is invalid")
}

this.id = id;
}

但是这是单个属性的判断,如果是多个属性联合判断就不满足了。比如要判断 id 小于 100 时,name 只能以 A 开头,这样在 Student 类内部就无法完成判断,只能让调用者进行判断,也就是说将这个控制权交给了调用方,万一调用者不进行判断,那这个限制条件就相当于无效了。

再说第二个问题,因为使用 set 的方式是先 new 一个空对象,再依次设置属性值的,那就存在一种情况,在 newset 的过程中,对象是无效的(因为其他的属性值并没有设置好),此时如果获取到这个对象就可能出错。

针对以上几个问题,就产生了构建者模式的方式来创建对象。构建者模式是先将属性值设置好,然后最后按照这些值进行判断(如果需要的化),然后创建出具体的对象。

构建者模式也很简单,只需要在需要使用构建者模式的类创建一个内部类,然后对每个属性产生一个方法,最后统一暴露对外 build 方法即可。

例子

上文 Student 类的构建者代码如下

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 Student {
private int id;

private String name;

public Student(int id, String name) {
this.id = id;
this.name = name;
}

private class Builder {
private int id;

private String name;

public Builder setId(int id) {
if (id < 0) {
throw new IllegalArgumentException("id is invalid");
}

this.id = id;
return this;
}

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

public Student build() {
// 在构建最后一步,进行必要的校验
if (id < 100 && !name.startsWith("A")) {
throw new IllegalArgumentException("The id or name is invalid");
}

return new Student(id, name);
}
}
}

// 使用的地方
Student student = new Student.Builder()
.setId(1)
.setName("Bob")
.build();

其他

构建者模式代码还是比较多的,如果构造函数或 set 方式能够满足要求,就没必要写太多代码用上构造者模式,或者使用 lombok 插件的 Builder 注解自动生成,不过使用 lombok 就需要项目组中所有成员都使用,不然就很乱。

参考资料

王争,《设计模式之美》