第2条:遇到多个构造器参数时,考虑用构建者Bulider
静态工厂和构造器有个共同的局限性:他们都不能很好的扩展大量的可选参数,
比如包装食品营养成分标签,含量,卡路里,等等,有可能会超过20个可选域,对于这样的类,应该用那种构造器或静态方法来写呢,
构造器:
程序员一般习惯采用重载构造器来实现,第一个构造器,提供一个必要参数,第二个构造器提供,一个必要参数,一个可选参数,依次类推.
public NutritionFacts(int servingSize){}
public NutritionFacts(int servingSize,int calories){}
public NutritionFacts(int servingSize,int calories,int fat){}
缺点
构造器调用通常需要许多本不想设置参数,但是不得不为他们传递值.随着参数数目的增加,就很快失去了控制。
简而言之,可伸缩构造器是可行,只是当有很多参数时,会让客户端代码很难编写,而且代码也很难阅读。
另一种可选的方案就是采用JavaBeans模式。
若采用这种模式,则先调用无参构造器来创建一个对象,然后分别调用不同的setter方法来设置必要参数和可选参数:
这种模式下调用一个午餐构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个可选参数.
public class NutritionFacts{
private int servingSize=-1;
private int servings=-1;
private int calories=0;
...getter setter省略
}
优点:弥补了重载构造器模式的不足,创建实例很容易,代码易读性强
NutritionFacts cocaCola=new NutritionFacts();
cocaCola.setServingSize(22);
cocaCola.setServings(22);
...
不幸的是,JavaBeans模式也有一些严重的缺陷。由于构造过程被分到了多个调用中,一个JavaBean在其构造过程中可能处于不一致的状态。类无法仅仅通过检查构造器参数的有效性来保证一致性。试图使用一个处于不一致状态的对象将会导致失败,而且这种失败远不像那些包含bug的代码,因此它调试起来非常困难。与此相关的另一个缺点是,JavaBeans模式阻止了把类做成不可变的可能性,这需要程序员付出额外的努力来确保线程安全。
还有第三种方案,而且这种方案结合了可伸缩构造器模式的安全性和JavaBeans模式的可阅读性。它就是Builder模式。
构建器模式:
1 public class NutritionFacts { 2 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 10 public static class Bulider{ 11 private int servingSize; 12 private int servings; 13 private int calories=0; 14 private int fat=0; 15 private int sodium=0; 16 private int carbohydrate=0; 17 18 public Bulider( int servingSize,int servings) { 19 this.servings=servings; 20 this.servingSize=servingSize; 21 } 22 public Bulider calories( int var) { 23 this.calories=var; 24 return this; 25 } 26 public Bulider fat( int var) { 27 this.fat=var; 28 return this; 29 } 30 public Bulider sodium( int var) { 31 this.sodium=var; 32 return this; 33 } 34 public Bulider carbohydrate( int var) { 35 this.carbohydrate=var; 36 return this; 37 } 38 39 public NutritionFacts bulid() { 40 return new NutritionFacts(this); 41 } 42 } 43 private NutritionFacts(Bulider bulider) { 44 servings=bulider.servings; 45 servingSize=bulider.servingSize; 46 calories=bulider.calories; 47 fat=bulider.calories; 48 sodium=bulider.sodium; 49 carbohydrate=bulider.carbohydrate; 50 } 51 52 53 } 54 //客户端测试 55 @Test 56 public void codeTest() { 57 NutritionFacts nutritionFacts=new NutritionFacts.Bulider(240, 8). 58 calories(100).sodium(33).carbohydrate(22).bulid(); 59 }
优势:
Builder模式很适合于类的层级结构。对于多个平行类,可以平行使用每个类对应的builder。抽象的类拥有抽象的builder
相较于构造器,builder的一个小优点是,builder能拥有多个可变参数,因为每一个参数都是在它自己的方法里指定的。或者,builder能将传入到不同方法里的参数聚合起来然后传入单个域里
Builder模式很灵活。单个builder能被用来构建多个对象。我们可以在多次的build方法之间对builder的参数进行调整,以此来改变创建的对象。builder可以在创建对象时自动填充一些值,
比如序列号,每次一个新的对象被创建时,builder都能自动对其值进行增加。
不足:
为了创建对象,必须先创建它的构建器,虽然创建构建器的开销在实践中可能不那么明显,但是在某些注重性能的情况下,可能就成问题,Bulider模式比重叠构造器模式更加冗长,
而且,假如一开始是用构造器或者静态工厂,但是随着类的演变,参数的数量开始失控时,若此时想转用builder,那些过时的构造器或者静态工厂将会显得有点尴尬。因此,最好一开始就使用builder。