静态工厂和构造器有个共同的局限性:都不能很好地扩展大量的可选参数
例:
考虑用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份含量,每罐含量,每份卡路里,还有超过20个可选域:总脂肪量,饱和脂肪量,转化脂肪量,胆固醇,钠等等。大多数的产品在某几个可选域中都会有非0的值。
对于这样的类,应该用哪种构造器或者静态方法来编写呢?
1. 重叠构造器 (telescoping constructor)
提供第一个只有必要参数的构造器
提供第二个包含必要参数且包含一个可选参数的构造器
提供第三个包含必要参数且包含两个可选参数的构造器
以此类推,最后一个构造器包含所有参数
如下是个简单的示例
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
| 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 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
| NutritionFacts cocaCola = new NutritionFacts(240,8,100,0,35,27);
|
如上初始化中 fat 的值为0,这个参数本是不用初始化的,就6个参数的情况下,还说的过去,随着参数增加,这样就不行了。
重叠构造器是可行的,但当参数增多时,客户端的代码将变得很难编写,阅读性也较差,并且如果初始化时容易填错顺序,导致运行时的错误。
2. JavaBeans模式
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
| public class NutritionFacts { private int servingSize = -1; private int servings = -1; private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0;
public NutritionFacts() { }
public void setServingSize(int servingSize) { this.servingSize = servingSize; }
public void setServings(int servings) { this.servings = servings; }
public void setCalories(int calories) { this.calories = calories; }
public void setFat(int fat) { this.fat = fat; }
public void setSodium(int sodium) { this.sodium = sodium; }
public void setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; } }
NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setServings(8); cocaCola.setCalories(100); cocaCola.setSodium(35); cocaCola.setCarbohydrate(27);
|
JavaBeans模式创建实例很容易,代码可读性也很强,但其有很严重的缺点:
构造的过程包含多个调用,在构造过程中JavaBeans可能处于不一致状态
例如一个线程正在使用setter初始化值,而另一个线程正用getter取得该对象的字段
使用JavaBeans模式则该类不可成为不可变类 (因为有setter访问器)
3. Builder模式
此模式有重叠构造器的安全性,也有JavaBeans模式的高可读性
不直接生成需要的对象,而是得到一个builder对象,调用所有的必要的构造器或静态工厂,设置每一个需要设置的参数,最后调用一个无参数的build方法来生成一个不可变对象。
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
| 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 NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; }
public static class Builder { private int servingSize; private int servings; private int calories; private int fat; private int sodium; private int carbohydrate;
public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int calories) { this.calories = calories; 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 NutritionFacts build() { return new NutritionFacts(this); } } }
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
|
Builder 模式的可读性提高了很多,代码简易。Builder模式模拟了了具名可选参数
如果该类包含多个字段,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与重叠构造器与JavaBeans相比要更加安全与易读。