静态工厂和构造函数有一个共同的限制:它们不能很好地扩展到大量可选参数,所以考虑通过构造器来构造一个类的实例。

<The rest of contents | 余下全文>

Telescoping constructor pattern

通常我们在创建一个类的实例时候都是用它的构造函数来创建。例如:

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
// Telescoping constructor pattern - does not scale well!
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // (per serving) optional
private final int fat; // (g/serving) optional
private final int sodium; // (mg/serving) optional
private final int carbohydrate; // (g/serving) optional

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

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

public NutritionFacts(int servingSize, int servings,
int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}

public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}

public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
1
2
NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);

但是这种方式存在很多不便之处:

  • 即使只需要设定一部分参数也要为不需要设置的参数设置默认值
  • 当参数过多的时候,尤其是存在许多同类型的参数的时候,很容易产生参数设置的混淆,这种混淆编译是不会发现的,在程序运行的时候才会产生错误
  • 代码不易读懂,用户使用函数时候需要参照api

JavaBeans Pattern

另外一种可选的方式是javabean的形式,其主要理念是通过空构造函数来创建一个实例,然后通过set方法对参数进行赋值。例如:

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
// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // Required; no default value
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public NutritionFacts() {
}

// Setters
public void setServingSize(int val) {
servingSize = val;
}

public void setServings(int val) {
servings = val;
}

public void setCalories(int val) {
calories = val;
}

public void setFat(int val) {
fat = val;
}

public void setSodium(int val) {
sodium = val;
}

public void setCarbohydrate(int val) {
carbohydrate = val;
}
}

这种模式没有Telescoping constructor pattern的任何缺点。

创建实例很容易,虽然有点冗长,并且很容易读取生成的代码:

1
2
3
4
5
6
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

但是,javabean模式本身有严重的缺点。因为构造是在多个调用之间分割的,所以JavaBean在构造过程中可能处于不一致的状态。

Builder pattern

客户机不直接生成所需的对象,而是使用所有必需的参数调用构造函数(或静态工厂)并获得一个构建器对象。然后客户端调用builder对象上类似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
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
/**
* @Author liu
* @Description //TODO
* @Date 9:08 2019/5/15
* @Param
* @return
**/
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 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 calories(int val) {
calories = val;
return this;
}

public Builder fat(int val) {
fat = val;
return this;
}

public Builder sodium(int val) {
sodium = val;
return this;
}

public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}

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

private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}

public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
}
}

Builder pattern也有缺点。为了创建对象,必须首先创建它的构建器。虽然在实践中创建这个构建器的成本不太可能显著,但在性能关键的情况下,它可能会成为一个问题。此外,Builder pattern的代码较为冗长,在设计构造函数或静态工厂有多个参数的类时,尤其是当许多参数是可选的或具有相同类型时,Builder模式是一个很好的选择,因为它更易读、更安全。