通常情况下,构造方法的调用需要许多你不想设置的参数,但是你不得不为它们传递一个值。
比如:
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
上边代码第四个参数传了一个默认值0,其实这个值没必要设置。这样很难编写客户端代码,而且很难读懂它。可以考虑使用下面的方式
// Builder Pattern
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 {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private 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.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
客户端调用
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中 JavaBean可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类不可变的可能性,并且需要增加工作确保线程安全。
抽象类中也可以有builder
package com.effective.java.paragraph2;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
/**
* Builder模式非常适合类层次结构。使用平行层次结构的builder,每次嵌套在相应的类中。抽象类有抽象类的builder;
* 具体类有具体类的builder。例如,代表各种pizza的根层次机构的抽象类Pizza
*
*/
public abstract class Pizza {
public enum Topping{HAM,MUSHROOM,ONION,PRPPER,SAUSAGE};
final Set<Topping> toppings;
//Pizza.Builder是有一个带有递归类型参数的泛型类型。这与抽象的self方法一起,允许方法链在子类中正常工作,
//而不需要强制转换。java缺乏自我类型的这种变通解决办法称为模拟自我类型的习惯用法
abstract static class Builder<T extends Builder<T>>{
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping){
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
protected abstract T self();
}
Pizza (Builder<?> builder){
toppings = builder.toppings.clone();
}
}
Pizza的子类
package com.effective.java.paragraph2;
import java.util.Objects;
public class NyPizza extends Pizza{
public enum Size{SMALL,MEDITUM,LARGE};
private final Size size;
public static class Builder extends Pizza.Builder<Builder>{
private final Size size;
public Builder (Size size){
this.size = Objects.requireNonNull(size);
}
@Override
public NyPizza build(){
//返回的是pizza的子类,这种技术,其一个子类的方法被声明为返回在超类中声明的返回类型的子类型
//称为 协变返回类型
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder){
super(builder);
size = builder.size;
}
public Size getSize() {
return size;
}
}
客户端调用基本和简单的builder相同
package com.effective.java.paragraph2;
import com.google.gson.Gson;
public class PizzaApp {
public static void main(String args[]){
NyPizza pizza = new NyPizza.Builder(com.effective.java.paragraph2.NyPizza.Size.SMALL).
addTopping(com.effective.java.paragraph2.Pizza.Topping.MUSHROOM).
addTopping(com.effective.java.paragraph2.Pizza.Topping.SAUSAGE).build();
System.out.println(new Gson().toJson(pizza));
//输出:{"size":"SMALL","toppings":["MUSHROOM","SAUSAGE"]}
}
}