第2条:遇到多个构造器参数时要考虑用构建器

第2条:遇到多个构造器参数时要考虑用构建器

  思考:考虑用一个类表示包装食品外显示的营养成分标签。这些标签有几个域是必须的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。大多数产品的某几个可选域中都会有几个非零的值。

  对于这样的类,应该采用哪种构造器或者静态方法来编写呢?程序员一向习惯用重叠构造器(telescoping constructor)模式,在这种模式下,你提供一个必要的参数构造器,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器包含所有的可选参数。下面有个示例,为了简单起见,它只显示四个可选域:

 1 public class NutritionFacts {
 2     private final int servingSize;        //(ml)                required
 3     private final int servings;            //(per container)    required
 4     private final int calories;            //                    optional
 5     private final int fat;                //(g)                optional
 6     private final int sodium;            //(mg)                optional
 7     private final int carbohydrate;        //(g)                optional
 8     public NutritionFacts(int servingsize, int servings) {
 9         this(servingsize,servings,0);
10     }
11     public NutritionFacts(int servingsize, int servings, int calories) {
12         this(servingsize,servings,calories,0);
13     }
14     public NutritionFacts(int servingsize, int servings, int calories, int fat) {
15         this(servingsize,servings,calories,fat,0);
16     }
17     public NutritionFacts(int servingsize, int servings, int calories, int fat, int sodium) {
18         this(servingsize,servings,calories,fat,sodium,0);
19     }
20     public NutritionFacts(int servingsize, int servings, int calories, int fat, int sodium, int carbohydrate) {
21         super();
22         this.servingSize = servingsize;
23         this.servings = servings;
24         this.calories = calories;
25         this.fat = fat;
26         this.sodium = sodium;
27         this.carbohydrate = carbohydrate;
28     }
29 }

方法的弊端:这个构造器通常需要许多你本不想设置的参数,但还是不得不为它们传递值。并且随着参数数目的增加,难以控制。

方法的总结:重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。用户无法快速明确的知道参数代表的意思。一串类型相同的参数,输入过程中出错,编译器不会出错,但是程序运行时会出现错误的行为。

第二种实现方法:JavaBeans模式,在这种模式下,调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个可选的相关参数:

 1 public class NutritionFacts {
 2     private int servingSize = -1;
 3     private int servings = -1;
 4     private int calories = 0;
 5     private int fat = 0;
 6     private int sodium = 0;
 7     private int carbohydrate = 0;
 8     public NutritionFacts() {}
 9     //Setters
10     public void setServingSize(int servingSize) {
11         this.servingSize = servingSize;
12     }
13     public void setServings(int servings) {
14         this.servings = servings;
15     }
16     public void setCalories(int calories) {
17         this.calories = calories;
18     }
19     public void setFat(int fat) {
20         this.fat = fat;
21     }
22     public void setSodium(int sodium) {
23         this.sodium = sodium;
24     }
25     public void setCarbohydrate(int carbohydrate) {
26         this.carbohydrate = carbohydrate;
27     };
28 }

这种模式弥补了重叠构造器模式的不足,就是创建实例很容易,这样产生的代码读起来也很容易:

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setFat(35);
cocaCola.setCarbohydrate(27);

 遗憾的是,JavaBeans模式自身有着很严重的缺点。因为构造过程被分到了几个调用中,在构造过程中JavaBeans可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败,这种失败与包含错误的代码大相径庭,因此它调试起来十分困难。与此相关的另一点不足在于,JavaBeans模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。

第三种实现方法:Builder模式的一种形式。不直接生成想要的对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。下面就是示例:

 1 private final int servingSize;        //(ml)                required
 2     private final int servings;            //(per container)    required
 3     private final int calories;            //                    optional
 4     private final int fat;                //(g)                optional
 5     private final int sodium;            //(mg)                optional
 6     private final int carbohydrate;        //(g)                optional
 7     
 8     public static class Builder{
 9         //Required parameters
10         private final int servingSize;
11         private final int servings;
12         //optional parameters - initialized to default values
13         private int calories = 0;
14         private int fat = 0;
15         private int sodium = 0;
16         private int carbohydrate = 0;
17         
18         public Builder(int servingSize,int servings) {
19             this.servingSize = servingSize;
20             this.servings = servings;
21         }
22         
23         public Builder calories(int val) {
24             calories = val;
25             return this;
26         }
27         public Builder fat(int val) {
28             fat = val;
29             return this;
30         }
31         public Builder carbohydrate(int val) {
32             carbohydrate = val;
33             return this;
34         }
35         public Builder sodium(int val) {
36             sodium = val;
37             return this;
38         }
39         
40         public NutritionFacts build() {
41             return new NutritionFacts(this);
42         }
43     }
44     
45     private NutritionFacts(Builder builder) {
46         servingSize = builder.servingSize;
47         servings = builder.servings;
48         calories = builder.calories;
49         fat = builder.fat;
50         sodium = builder.sodium;
51         carbohydrate = builder.carbohydrate;
52         
53     }

注意NutritionFacts是不可变的,所有的默认参数值都单独的放在一个地方,builder的setter方法返回builder本身,以便可以把调用链接起来。下面是客户端代码:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 80).calories(100).sodium(35).carbohydrate(27).build();

 简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与使用传统的重叠构造器模式相比,使用Builder模式的客户端代码将更易于都和编写,构建器也比JavaBeans更加安全。

posted @ 2018-12-01 21:43  凭栏倚窗  阅读(235)  评论(0编辑  收藏  举报