遇到多个构造器参数时考虑用构建器
静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。当有超过20个可选域是必须的时候,对于此种情况,程序员一般考虑采用重叠构造器模式。这种模式下,提供第一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,以此类推,最后一个构造器包含所有的参数。
重叠构造器模式
含有四个可选域的情况
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
   | 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, servings, calories, fat, sodium, carbohydrate);     }      }
  | 
 
当你想创建实例的时候,就利用参数列表最短的构造器。
重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且较难阅读,使用的时候容易混淆部分参数容易出错
JavaBeans模式
这种模式调用一个无参构造器来创建对象,然后用setter方法来设置必要的参数以及相关参数的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | 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 val) { servingSize = val; }     public void setServings(int val) { servings = val; }     public void setCalories(int val) { calories = val; }     public void setFat(int val) { fat = val; }     public void setSodium(int val) { sodium = val; }     public void setCarbohydrate(int val) { carbohydrate = val; } }     
  | 
 
这种方式弥补了重叠构造器模式的不足,创建实例容易,阅读代码也容易。
1 2 3 4 5
   | NutritionFacts cocaCola = new  NutritionFacts(); cocaCola.setServingSize(10); cocaCola.setServings(10); cocaCola.setCalories(10); cocaCola.setFat(10);
   | 
 
遗憾的是自身有严重的缺陷。构造过程被分到了几个调用中,构造过程中JavaBean可能处于不一致状态的对象,将会导致失败。类无法仅仅通难过校验构造器参数的有效性来保证一致性,Javabeans模式阻止了把类做成不可变的可能,需要付出额外的努力来确保它的线程安全。
Builder模式
既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那样的可读性。
不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象,这个builder是它构建的类的静态成员类。
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
   | 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 static class Builder{         private final int servingSize;          private final int servings;            private final int calories = 0;           private final int fat = 0;               private final int sodium = 0;            private final int carbohydrate = 0;                   public Builder(int servingSize, int servings){             this.servingSize = servingSize;             this.servings =servings;         }                  public Builder calories(int val){             calories = val;             return this;         }                  public Builder fat(int val){             fat = val;             return this;         }                  public Builder sodium(int val){             sodium = val;             return this;         }                  public Builder carbohydrate(int val){             carbohydrate = val;             return this;         }                  public NutritionFacts build(){             return new NutritionFacts(this);         }     }          private NutritionFacts(Builder builder){         servingSize = builder.servingSize;          servings = builder.servingSize;            calories = builder.servingSize;           fat = builder.servingSize;               sodium = builder.servingSize;            carbohydrate = builder.servingSize;      } }
  | 
 
注意NutritionFacts是不可变的,所有的默认参数值都单独放一个地方。builder的setter方法返回builder本身,以便可以把调用用调用链连接起来。
1
   | NutritionFacts cocaCola = new NutritionFacts.Builder(200,20).calories(10).fat(15).sodium(10).build();
   | 
 
builder像个构造器一样,可以对其参数强加约束条件。build方法可以检验这些约束条件,将参数从builder拷贝到对象中之后,并在对象域而不是builder域中对他们进行校验,这一点很重要。如果违反了任何约束条件,build方法就应该抛出IllegalStateException,显示违背了哪个约束条件。
对多个参数强加约束条件的另一个方法,用多个setter方法对某个约束条件必须持有的所有参数进行检查。如果该约束条件没有得到满足,setter方法就抛出IllegalStateException,不用等到在build的时候。
设置了参数的builder生成了一个很好的抽象工厂,客户端可以将这样一个builder传给方法,使该方法能够为客户端创建一个或者多个对象。要使用这种用法,需要有个类型来表示builder,只要一个泛型就能满足所有的builder,无论他们在构建哪种类型的对象:
1 2 3
   | public interface Builder<T>{     public T build(); }
  | 
 
可以声明NutritionFacts.Builder类来实现Builder<NutritionFacts>  。
带有Builder实例的方法通常利用有限制的通配符类型来约束构建器的类型参数。eg.下面就是构建每个节点的方法,它利用一个客户端提供的Builder实例来构建树:
1
   | Tree buildTree(Builder<? extends Node> nodeBuilder){ ... }
  | 
 
**Builder模式还比重叠构造器模式更加冗长,因此它只有在很难参数的时候才使用,比如4个或者更多。但是你要记住,将来可能添加参数。简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是大多参数都是可选的时候。代码易于阅读编写,构建器也比JavaBeans更加安全。