Builder模式的思考,Effective Java

《Effective Java》(第2版)中第二条中提到:遇到多个构造器参数时要考虑用构建器。在复习static关键字和内部类时回头看了一下,这才明白了为什么要用静态内部类来做处理,这里记录一下。

先看再看一下《Effective Java》书中的例子,例子中是用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个是必需的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等。如果用一般的处理方式,直接把这些参数在构造器中初始化的话,有两个缺点:

1、没法体现营养成分的必需和非必需属性

2、参数太多,这样在实例化传参数的时候其实比较难区分的。因为他们几乎都是int型变量,而且即使传递错了也不会有任何提醒。

基于上面的问题,书中转而使用Builder模式,当然,Builder模式能解决上面的问题,那么也间接说明了Builder模式的优点。(1、区分必传和选传参数2、比较明确的表示出参数的含义)

这里直接把书中代码搬过来:

 1 public class NutritionFacts {
 2     private final int servingSize;//份容量
 3     private final int servings;//份
 4     private final int calories;//卡路里
 5     private final int fat;//脂肪
 6     private final int sodium;//钠
 7     private final int carbohydrate;//糖类
 8     public static class Builder{
 9         private final int servingSize;//份容量
10         private final int servings;//份
11         //必要参数
12         public Builder(int servingSize,int servings){
13             this.servingSize=servingSize;
14             this.servings=servings;
15         }
16         private  int calories = 0;
17         private  int fat = 0;
18         private  int sodium = 0;
19         private  int carbohydrate = 0;
20         public Builder calories(int val){
21             calories = val;
22             return this;
23         }
24         public Builder fat(int val){
25             fat = val;
26             return this;
27         }
28         public Builder sodium(int val){
29             sodium = val;
30             return this;
31         }
32         public Builder carbohydrate(int val){
33             carbohydrate = val;
34             return this;
35         }
36         public NutritionFacts build(){
37             return new NutritionFacts(this);
38         }
39     }
40     private NutritionFacts(Builder builder){
41         servingSize = builder.servingSize;
42         servings = builder.servings;
43         calories = builder.calories;
44         fat = builder.fat;
45         sodium = builder.sodium;
46         carbohydrate = builder.carbohydrate;
47     }
48 }

使用时比较简单,而且比较清晰:

1 NutritionFacts cocoCola = new NutritionFacts.Builder(20,15).calories(11).carbohydrate(12).build();

注意点:

1、对于NutritionFacts来说,包含了三部分,final成员变量,私有构造器、静态内部类。final成员变量保证了初始化安全性,即在构造器执行完成之前必须显示对成员变量初始化。这里是在构造函数中传入Builder来对其初始化。

2、构造器为什么要声明为私有的?保证了外部创建实例时只能通过静态内部类的build()方法来实现。

3、为什么是静态内部类?因为构造器是私有的,导致外部只能通过内部类的build()方法来实现。而非静态内部类对象的创建又依赖于外部类对象,即必须有外部类对象来创建(外部类对象.new InnerClassName()),这样就陷入了死循环。而静态内部类不需要依赖于外部类对象,只需要通过 “new OutClassName.InnerClassName()”就可以完成实例化。

4、内部类Builder通过final关键字来区分必需参数和非必需参数。通过builder()方法完成外部类实例化。这里利用内部类可以访问外部私有元素的特性。

5、总的来看,就是外部类通过静态内部类完成了自己成员变量的初始化。

参考书籍:

《Effective Java》