Effective Java (2nd Edition)读书笔记-Item 2: 当面对众多的构造函数的时候选择一个建造者(builder)
静态工厂方法和public构造函数有一个共同的弊病:在大量的可选参数面前,他们不能很好的体现可伸展性。可以看一个例子如下:
1
//
Telescoping constructor pattern - does not scale well!
2 public class NutritionFacts {
3private final int servingSize; // (mL) required
4private final int servings; // (per container) required
5private final int calories; // optional
6private final int fat; // (g) optional
7private final int sodium; // (mg) optional
8private final int carbohydrate; // (g) optional
9public NutritionFacts(int servingSize, int servings) {
10this(servingSize, servings, 0);
11}
12public NutritionFacts(int servingSize, int servings,
13int calories) {
14this(servingSize, servings, calories, 0);
15}
16public NutritionFacts(int servingSize, int servings,
17int calories, int fat) {
18this(servingSize, servings, calories, fat, 0);
19}
20public NutritionFacts(int servingSize, int servings,
21int calories, int fat, int sodium) {
22this(servingSize, servings, calories, fat, sodium, 0);
23}
24public NutritionFacts(int servingSize, int servings,
25int calories, int fat, int sodium, int carbohydrate) {
26this.servingSize = servingSize;
27this.servings = servings;
28this.calories = calories;
29this.fat = fat;
30this.sodium = sodium;
31this.carbohydrate = carbohydrate;
32}
33}
34
2 public class NutritionFacts {
3private final int servingSize; // (mL) required
4private final int servings; // (per container) required
5private final int calories; // optional
6private final int fat; // (g) optional
7private final int sodium; // (mg) optional
8private final int carbohydrate; // (g) optional
9public NutritionFacts(int servingSize, int servings) {
10this(servingSize, servings, 0);
11}
12public NutritionFacts(int servingSize, int servings,
13int calories) {
14this(servingSize, servings, calories, 0);
15}
16public NutritionFacts(int servingSize, int servings,
17int calories, int fat) {
18this(servingSize, servings, calories, fat, 0);
19}
20public NutritionFacts(int servingSize, int servings,
21int calories, int fat, int sodium) {
22this(servingSize, servings, calories, fat, sodium, 0);
23}
24public NutritionFacts(int servingSize, int servings,
25int calories, int fat, int sodium, int carbohydrate) {
26this.servingSize = servingSize;
27this.servings = servings;
28this.calories = calories;
29this.fat = fat;
30this.sodium = sodium;
31this.carbohydrate = carbohydrate;
32}
33}
34
当我们需要创建实例的时候,需要知道而且传递每个参数的值到构造函数中。例如:
1
NutritionFacts cocaCola
=
2 new NutritionFacts( 240 , 8 , 100 , 0 , 35 , 27 );NutritionFacts cocaCola =
3 new NutritionFacts( 240 , 8 , 100 , 0 , 35 , 27 );
2 new NutritionFacts( 240 , 8 , 100 , 0 , 35 , 27 );NutritionFacts cocaCola =
3 new NutritionFacts( 240 , 8 , 100 , 0 , 35 , 27 );
解决这个问题的方法可以使用JavaBeans pattern:此方式中你可以调用无参数的构造函数来创建对象,然后调用setter方法来set每个需要去的参数和可选的参数项。 例如
1
//
JavaBeans Pattern - allows inconsistency, mandates mutability
2 public class NutritionFacts {
3// Parameters initialized to default values (if any)
4private int servingSize = -1; // Required; no default value
5private int servings = -1; // " " " "
6private int calories = 0;
7private int fat = 0;
8private int sodium = 0;
9private int carbohydrate = 0;
10public NutritionFacts() { }
11// Setters
12public void setServingSize(int val) { servingSize = val; }
13public void setServings(int val) { servings = val; }
14public void setCalories(int val) { calories = val; }
15public void setFat(int val) { fat = val; }
16public void setSodium(int val) { sodium = val; }
17public void setCarbohydrate(int val) { carbohydrate = val; }
18}
19
2 public class NutritionFacts {
3// Parameters initialized to default values (if any)
4private int servingSize = -1; // Required; no default value
5private int servings = -1; // " " " "
6private int calories = 0;
7private int fat = 0;
8private int sodium = 0;
9private int carbohydrate = 0;
10public NutritionFacts() { }
11// Setters
12public void setServingSize(int val) { servingSize = val; }
13public void setServings(int val) { servings = val; }
14public void setCalories(int val) { calories = val; }
15public void setFat(int val) { fat = val; }
16public void setSodium(int val) { sodium = val; }
17public void setCarbohydrate(int val) { carbohydrate = val; }
18}
19
这样可以很容易的阅读和很容易的调用。
1
NutritionFacts cocaCola
=
new
NutritionFacts();
2 cocaCola.setServingSize( 240 );
3 cocaCola.setServings( 8 );
4 cocaCola.setCalories( 100 );
5 cocaCola.setSodium( 35 );
6 cocaCola.setCarbohydrate( 27 );
2 cocaCola.setServingSize( 240 );
3 cocaCola.setServings( 8 );
4 cocaCola.setCalories( 100 );
5 cocaCola.setSodium( 35 );
6 cocaCola.setCarbohydrate( 27 );
但是,JavaBeans模式也有严重的问题存在。因为构造函数被分割成多个调用,JavaBean可能在和构造函数不能保持一致性。JavaBean阻止了让类成为immutable的可能。
可以结合使用构造函数模式的安全性和JavaBeans模式的易读性。它就是Builder模式。
1.客户端使用所有必须参数去调用构造函数来得到一个builder对象。
2.客户端在builder上调用类似于setter的方法设置每个可选项参数。
3.客户端调用无参数的build方法来生成对象,这个对象是immutable的。
注意builder是要构造类的静态成员类。如下例所示:
1
//
Builder Pattern
2 public class NutritionFacts {
3 private final int servingSize;
4 private final int servings;
5 private final int calories;
6 private final int fat;
7 private final int sodium;
8 private final int carbohydrate;
9 public static class Builder {
10 // Required parameters
11 private final int servingSize;
12 private final int servings;
13 // Optional parameters - initialized to default values
14 private int calories = 0 ;
15 private int fat = 0 ;
16 private int carbohydrate = 0 ;
17 private int sodium = 0 ;
18 public Builder( int servingSize, int servings) {
19 this .servingSize = servingSize;
20 this .servings = servings;
21 }
22 public Builder calories( int val)
23 { calories = val; return this ; }
24 public Builder fat( int val)
25 { fat = val; return this ; }
26 public Builder carbohydrate( int val)
27 { carbohydrate = val; return this ; }
28 public Builder sodium( int val)
29 { sodium = val; return this ; }
30 public NutritionFacts build() {
31 return new NutritionFacts( this );
32 }
33 }
34 private NutritionFacts(Builder builder) {
35 servingSize = builder.servingSize;
36 servings = builder.servings;
37 calories = builder.calories;
38 fat = builder.fat;
39 sodium = builder.sodium;
40 carbohydrate = builder.carbohydrate;
41 }
42 }
43
2 public class NutritionFacts {
3 private final int servingSize;
4 private final int servings;
5 private final int calories;
6 private final int fat;
7 private final int sodium;
8 private final int carbohydrate;
9 public static class Builder {
10 // Required parameters
11 private final int servingSize;
12 private final int servings;
13 // Optional parameters - initialized to default values
14 private int calories = 0 ;
15 private int fat = 0 ;
16 private int carbohydrate = 0 ;
17 private int sodium = 0 ;
18 public Builder( int servingSize, int servings) {
19 this .servingSize = servingSize;
20 this .servings = servings;
21 }
22 public Builder calories( int val)
23 { calories = val; return this ; }
24 public Builder fat( int val)
25 { fat = val; return this ; }
26 public Builder carbohydrate( int val)
27 { carbohydrate = val; return this ; }
28 public Builder sodium( int val)
29 { sodium = val; return this ; }
30 public NutritionFacts build() {
31 return new NutritionFacts( this );
32 }
33 }
34 private NutritionFacts(Builder builder) {
35 servingSize = builder.servingSize;
36 servings = builder.servings;
37 calories = builder.calories;
38 fat = builder.fat;
39 sodium = builder.sodium;
40 carbohydrate = builder.carbohydrate;
41 }
42 }
43
注意NutritionFacts是immutable, 所有参数的缺省值是在单一位置。builder的setter方法返回builder自身,因此调用者可以被链接到。因此客户端代码如下:
1
NutritionFacts cocaCola
=
new
NutritionFacts.Builder(
240
,
8
)
2 calories( 100 ).sodium( 35 ).carbohydrate( 27 ).build();
Builder模式模仿了Ada和Python的可选参数命名法则。
2 calories( 100 ).sodium( 35 ).carbohydrate( 27 ).build();
Builder模式超越构造函数的一点优势是builder能够拥有多个变参。构造函数类似于函数只能有一个变参。
总而言之,客户端传递builder给方法能使方法在客户端创建一个或者多个对象。为了使用这种用法,需要用一种类型表示builder。如下所示:
1
//
A builder for objects of type T
2 public interface Builder < T > {
3public T build();
4}
NutritionFacts.Builder声明为实现Builder<NutritionFacts>.
2 public interface Builder < T > {
3public T build();
4}
Builder模式也有自己的缺点。为了创建一个对象,首先需要创建它的builder。然而创建builder的成本不大可能在实践中被注意到。但是在一些严格要求性能的情况下也会存在问题。
总结:
当构造函数或者静态工厂要处理大量参数,尤其其中很多参数是可选的时候,Builder模式是个不错的选择。
Client代码将会易读写,而且比JavaBeans更安全。
In summary, the Builder pattern is a good choice when designing classes
whose constructors or static factories would have more than a handful of
parameters, especially if most of those parameters are optional. Client code is
much easier to read and write with builders than with the traditional telescoping
constructor pattern, and builders are much safer than JavaBeans.