今天在看当当网的开源项目分布式任务调度框架源码的时候,感到很奇怪,代码如下
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class LiteJobConfiguration implements JobRootConfiguration {
private final JobTypeConfiguration typeConfig;
private final boolean monitorExecution;
private final int maxTimeDiffSeconds;
private final int monitorPort;
private final String jobShardingStrategyClass;
private final int reconcileIntervalMinutes;
private final boolean disabled;
private final boolean overwrite;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public static class Builder {
private final JobTypeConfiguration jobConfig;
private boolean monitorExecution = true;
private int maxTimeDiffSeconds = -1;
private int monitorPort = -1;
private String jobShardingStrategyClass = "";
private boolean disabled;
private boolean overwrite;
private int reconcileIntervalMinutes = 10;
/*
此处省略各属性的this setter调用链
*/
public final LiteJobConfiguration build() {
return new LiteJobConfiguration(jobConfig, monitorExecution, maxTimeDiffSeconds, monitorPort, jobShardingStrategyClass, reconcileIntervalMinutes, disabled, overwrite);
}
}
}
可以看到这段代码有以下特征
1. 外部类有非常多的属性,外部类有的属性内部类都有,并且外部类的属性是final类型的
2. 外部类的构造方法是私有的
3. 内部类有一个build方法用于实例化LiteJobConfiguration对象
这样设计类的好处如下
1. 避免恼人的不必要参数设置
2. 利用set设置不可变属性
先看看我们在不使用builder内部类的时候如何实例化这样的对象
有如下这样的饮料bean,可以代表咖啡,可乐或者果汁,它包含的属性中energy和carbohydrate必须要被设置,其它属性选择设置
public class NutritionFact {
private final int energy;
private final int carbohydrate;
private final int protein;
private final int sodium;
private final int fat;
private final int vitaminC;
public NutritionFact(int energy, int carbohydrate, int protein, int sodium, int fat,int vitaminC) {
this.energy = energy;
this.carbohydrate = carbohydrate;
this.protein = protein;
this.sodium = sodium;
this.fat = fat;
this.vitaminC = vitaminC;
}
//getters ...
}
像上面这样的类我们需要进行如下这样的初始化
NutritionFact colaNutritionFact = new NutritionFact(1, 2, 0, 5, 0, 0);
System.out.println(colaNutritionFact);
可以看到虽然我们只要设置energy,carbohydrate,sodium属性,但是我们还是得在构造器中设置每个参数,其中不需要设置的参数必须设置为0
有人可能会说,可以利用构造函数重载
public NutritionFact(int energy, int carbohydrate, int protein) {
this.energy = energy;
this.carbohydrate = carbohydrate;
this.protein = protein;
this.sodium=0;
this.fat=0;
this.vitaminC=0;
}
但是这没有解决参数0设置问题,只不过将调用该类的使用者设置0参数转化为开发该类的开发者设置
还有人会说,我们可以设置如下构造函数
public NutritionFact(int energy, int carbohydrate) {
this.energy = energy;
this.carbohydrate = carbohydrate;
}
先利用构造函数设置必填参数,然后利用setter选填设置参数,但是这个类所有参数都是final类型的,必须在实例化对象时就设置所有参数
我们利用当当网那样的设计来解决这个问题
public class NewNutritionFact {
/*
可以根据需要将这些属性全部设置为final类型
*/
private final int energy;
private final int carbohydrate;
private final int protein;
private final int sodium;
private final int fat;
private final int vitaminC;
public NewNutritionFact(NutritionFactBuilder builder) {
this.energy = builder.energy;
this.carbohydrate = builder.carbohydrate;
this.protein = builder.protein;
this.sodium = builder.sodium;
this.fat = builder.fat;
this.vitaminC = builder.vitaminC;
}
/*
public static类型让外部可以访问
*/
public static class NutritionFactBuilder {
/*
以下两个是必须属性
*/
private int energy;
private int carbohydrate;
/*
以下四个是可选属性
*/
private int protein;
private int sodium;
private int fat;
private int vitaminC;
/*
必须要设置的两个参数
*/
public NutritionFactBuilder(int energy, int carbohydrate) {
this.energy = energy;
this.carbohydrate = carbohydrate;
}
/**
* 链式调用
*
* @param protein
* @return
*/
public NutritionFactBuilder setProtein(int protein) {
this.protein = protein;
return this;
}
public NutritionFactBuilder setSodium(int sodium) {
this.sodium = sodium;
return this;
}
public NutritionFactBuilder setFat(int fat) {
this.fat = fat;
return this;
}
public NutritionFactBuilder setVitaminC(int vitaminC) {
this.vitaminC = vitaminC;
return this;
}
public NewNutritionFact build() {
return new NewNutritionFact(this);
}
}
}
实例化过程如下
public void innerBuilder() {
NewNutritionFact colNutritionFact = new NewNutritionFact.NutritionFactBuilder(255, 12).setSodium(5).build();
System.out.println(colNutritionFact);
NewNutritionFact coffeeNutritionFact = new NewNutritionFact.NutritionFactBuilder(255, 11).setFat(1).build();
System.out.println(coffeeNutritionFact);
}
配合上调用链代码非常的简洁,
总结一下,如果需要设计一个拥有很多可选属性类的时候,特别是其为不可变类的时候使用内部类Builder是个不错的选择