[Effective Java Distilled] Item 2 当构造方法中有多个参数时,考虑建造者模式(二)

2014-11-24 11:20:11 · 作者: · 浏览: 16
{ fat = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
相应构造过程的代码为:
[java]
NutritionFactscocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
以上使用链式赋值,看上去比使用setters更加自然,同时代码冗余也更少。使用链式赋值的方式也神似使用命名参数进行赋值的场景,上面的calories,sodium以及carbohydrate等可选参数可以被看做key,后面接的参数则是value。链式操作的关键点就是链中的每一个方法的返回类型都是该类型本身。
下面来看看建造者模式是如何克服以上Telescoping Constructor模式和JavaBean模式的各项缺点的:
TC模式中构造方法规模过大的问题
显然,使用建造者模式之后,构造方法只有两个,一个是建造者本身的公用的构造方法,它只会带有所有必选的参数;另一个是实体类本身声明为private的构造方法,它仅能通过建造者实例调用。因此,实际公开给外部代码使用的构造方法也只有一个而已。
TC模式中参数容易混淆的问题
对于必选参数数量有限,而可选参数数量十分多的用例,比如上面的建造者对象中的构造方法只有2个必选参数,因为能够回避在构造方法中声明任何可选参数,所以建造者模式也极大的降低了参数混淆的问题,只有2个参数如果还能混淆,那只能呵呵了……
JavaBean模式中构造代码冗长的问题
使用链式赋值方式,可以减少构造代码的长度。
JavaBean模式中构造过程非原子性的问题
在使用建造者模式来构造对象时,只有当调用了建造者实例的build方法后,对象才能被正常访问,而且当build方法被调用之后,所有的域都会被正常初始化,不存在使用setters时,对象初始化不完整的情况。
JavaBean模式中强制性声明类为非可变性的问题
从上面的示例代码中,可以看到实体类本身的所有域都被声明为private final,它们没有setters方法,也就是说,一旦对象初始化完毕,它的任何域就不可改变了。这种做法让实体类称为不可变类,从而让该类型在并发环境中能够被更加安全而高效的使用。
JavaBean模式中setters方法可能破坏不变性约束的问题
这一点,在建造者对象的build方法中可以进行控制。因为build方法负责实例化实体类,而在实例化实体类之前,可以对一些参数进行检查,如果不满足不变性约束,可以根据场景抛出异常,如果没有自定义的异常,一般选择抛出IllegalStateException。
泛化建造者模式
在Item 1中,提到了静态工厂方法和简单工厂模式有几分相似。这是通过声明静态工厂方法的返回值为父类型来实现的。而利用泛型,则可以让建造者模式和抽象工厂模式进行融合,通过下面这个接口:
[java]
// A builder for objects of type T
public interface Builder {
public T build();
}
为何这里是和抽象工厂模式进行了融合,而不是和简单工厂模式进行融合,这是因为上面定义的接口让构造出来的实际对象能够最多在两个维度上变化。
首先,T本身就是一个维度,这里的T只是一个占位符(placeholder),它能代表任何类型,包括接口类型。如果T是一个接口类型或者某个类层次上的父类的话,那么就开启了第二个维度,因为它本身又代表了一系列的对象。这样一来,后面的事情就类似简单工厂模式了。同样地,如果T类型实际上是一个具体的类,比如上面提到的NutritionFacts,那么抽象工厂模式就退化成了简单工厂模式。
就上面的例子而言,对Builder的声明应该改成:
publicclass NutritionFacts {
…...
public static class NutritionFactsBuilderimplementsBuilder {
……
}
}
引用《Effective Java》中关于Item 2的总结:
当构造方法或者静态工厂方法中的参数过多的时候,尤其是可选参数很多时,考虑使用建造者模式吧。它比Telescoping Constructor模式更易读,更简练。同时也比JavaBean模式更加安全。