靜態工廠方法和public構造函數有一個共同的弊病:在大量的可選參數面前,他們不能很好的體現可伸展性。可以看一個例子如下:
1
// Telescoping constructor pattern - does not scale well!
2
public class NutritionFacts
{
3
private final int servingSize; // (mL) required
4
private final int servings; // (per container) required
5
private final int calories; // optional
6
private final int fat; // (g) optional
7
private final int sodium; // (mg) optional
8
private final int carbohydrate; // (g) optional
9
public NutritionFacts(int servingSize, int servings)
{
10
this(servingSize, servings, 0);
11
}
12
public NutritionFacts(int servingSize, int servings,
13
int calories)
{
14
this(servingSize, servings, calories, 0);
15
}
16
public NutritionFacts(int servingSize, int servings,
17
int calories, int fat)
{
18
this(servingSize, servings, calories, fat, 0);
19
}
20
public NutritionFacts(int servingSize, int servings,
21
int calories, int fat, int sodium)
{
22
this(servingSize, servings, calories, fat, sodium, 0);
23
}
24
public NutritionFacts(int servingSize, int servings,
25
int calories, int fat, int sodium, int carbohydrate)
{
26
this.servingSize = servingSize;
27
this.servings = servings;
28
this.calories = calories;
29
this.fat = fat;
30
this.sodium = sodium;
31
this.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);
解決這個問題的方法可以使用JavaBeans pattern:此方式中你可以調用無參數的構造函數來創建對象,然后調用setter方法來set每個需要去的參數和可選的參數項。 例如
1
// JavaBeans Pattern - allows inconsistency, mandates mutability
2
public class NutritionFacts
{
3
// Parameters initialized to default values (if any)
4
private int servingSize = -1; // Required; no default value
5
private int servings = -1; // " " " "
6
private int calories = 0;
7
private int fat = 0;
8
private int sodium = 0;
9
private int carbohydrate = 0;
10
public NutritionFacts()
{ }
11
// Setters
12
public void setServingSize(int val)
{ servingSize = val; }
13
public void setServings(int val)
{ servings = val; }
14
public void setCalories(int val)
{ calories = val; }
15
public void setFat(int val)
{ fat = val; }
16
public void setSodium(int val)
{ sodium = val; }
17
public 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);
但是,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
注意NutritionFacts是immutable, 所有參數的缺省值是在單一位置。builder的setter方法返回builder自身,因此調用者可以被鏈接到。因此客戶端代碼如下:
1
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
2
calories(100).sodium(35).carbohydrate(27).build();
Builder模式模仿了Ada和Python的可選參數命名法則。
Builder模式超越構造函數的一點優勢是builder能夠擁有多個變參。構造函數類似于函數只能有一個變參。
總而言之,客戶端傳遞builder給方法能使方法在客戶端創建一個或者多個對象。為了使用這種用法,需要用一種類型表示builder。如下所示:
1
// A builder for objects of type T
2
public interface Builder<T>
{
3
public T build();
4
}
NutritionFacts.Builder聲明為實現Builder<NutritionFacts>.
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.
posted on 2008-06-17 21:47
一葉笑天 閱讀(314)
評論(0) 編輯 收藏