遇到多个构造器参数时要考虑使用builder

遇到多个构造器参数时要考虑使用builder

概述

静态工厂和构造函数都不能很好地扩展到大量的可选参数。

场景

考虑这样一个类——营养成分标签,其中有几个field时必需的,其他则是可选的:

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
class NutritionFactsOne {
private int servingSize; //require
private int servings; //require
private int calorries;
private int fat;
private int sodium;
private int carbohydrate;

public NutritionFactsOne(int servingSize, int servings) {
this(servingSize, servings, 0);
}

public NutritionFactsOne(int servingSize, int servings, int calorries) {
this(servingSize, servings, calorries, 0);
}

public NutritionFactsOne(int servingSize, int servings, int calorries, int fat) {
this(servingSize, servings, calorries, fat, 0);
}

public NutritionFactsOne(int servingSize, int servings, int calorries, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calorries = calorries;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}

}

这样,我们创建一个实例时,就很不容易阅读:

1
NutritionFactsOne n = new NutritionFactsOne(240, 8, 100, 0, 35, 27);

当然,也可以考虑用java beans的模式,然后调用setter设置每个必要的参数,已经相关的可选参数。

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
class NutritionFactsTwo {
private int servingSize; //require
private int servings; //require
private int calorries;
private int fat;
private int sodium;
private int carbohydrate;

public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}

public void setServings(int servings) {
this.servings = servings;
}

public void setCalorries(int calorries) {
this.calorries = calorries;
}

public void setFat(int fat) {
this.fat = fat;
}

public void setSodium(int sodium) {
this.sodium = sodium;
}

public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}

紧接着

1
2
3
4
5
NutritionFactsTwo n = new NutritionFactsTwo();
n.setServingSize(240);
n.setServings(8);
n.setCalorries(100);
n.setFat(35);

但这个使用方式,在多线程的环境下有着致命的缺点,因为这有可能使得对象处于一个不一致的状态。

因此,我们可以使用builder的一种模式:

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
class NutritionFactsThree {
private final int servingSize; //require
private final int servings; //require
private final int calorries;
private final int fat;
private final int sodium;
private final int carbohydrate;

public static class Builder {
private final int servingSize; //require
private final int servings; //require

private int calorries = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;


public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calorries(int calorries){
this.calorries = calorries;
return this;
}
public Builder fat(int fat){
this.fat = fat;
return this;
}
public Builder sodium(int sodium){
this.sodium = sodium;
return this;
}
public Builder carbohydrate(int carbohydrate){
this.carbohydrate = carbohydrate;
return this;
}
public NutritionFactsThree build(){
return new NutritionFactsThree(this);
}
}
private NutritionFactsThree(Builder builder){
servingSize = builder.servingSize;
servings = builder.servings;
calorries = builder.calorries;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}

builder的setter方法返回builder本身,这样就可以把调用链给连接起来:

1
2
3
4
5
NutritionFactsThree n = new NutritionFactsThree.Builder(240, 8)
.calorries(0)
.sodium(35)
.carbohydrate(27)
.build();

优点

  1. 客户端代码易于理解
  2. 可以对参数施加约束条件
  3. builder有多个可变参数
  4. builder相比javabeans更加安全

总结

如果类的构造器或者静态工厂中具有多个参数,设计类的时候,builder模式应该优先被选择,特别是大多数参数都optional的时候。