遇到多个构造器参数时考虑用构建器
静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。当有超过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
| 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更加安全。