一、引言
在创建对象时,我们有可能遇到一个类中构造器可选参数太多,不好初始化。我们最先想到的解决方案是重叠构造器、JavaBean模式,其实还有一种方案:Builder构建器。
二、类比
2.1 重叠构造器
重叠构造器其实就是,构造器之间的调用。先让我们看下java.util.Date类中是怎么使用重叠构造器来初始化时间的:
//年月日
public Date(int year, int month, int date) {
this(year, month, date, 0, 0, 0);
}
//年月日时分
public Date(int year, int month, int date, int hrs, int min) {
this(year, month, date, hrs, min, 0);
}
//年月日时分秒
public Date(int year, int month, int date, int hrs, int min, int sec) {
int y = year + 1900;
// month is 0-based. So we have to normalize month to support Long.MAX_VALUE.
if (month >= 12) {
y += month / 12;
month %= 12;
} else if (month < 0) {
y += CalendarUtils.floorDivide(month, 12);
month = CalendarUtils.mod(month, 12);
}
BaseCalendar cal = getCalendarSystem(y);
cdate = (BaseCalendar.Date) cal.newCalendarDate(TimeZone.getDefaultRef());
cdate.setNormalizedDate(y, month + 1, date).setTimeOfDay(hrs, min, sec, 0);
getTimeImpl();
cdate = null;
}
看起来也不是很麻烦而且在一定程度上缓解参数多的问题,但是如果我们再加入毫秒、微秒、纳秒呢?这时随着参数的增加,利用重叠构造器初始化对象就会变得很难维护,而且客户端也变得不易调用和阅读。
2.2 Java Bean模式
什么是JavaBean模式?其实这个东西,我们经常用到:先调用一个无参构造器来创建对象,然后再调用 setter 方法来设置每个必要的参数,以及每个相关的可选参数。
还是以java.util.Date类为例,如下图:
这种模式弥补了重叠构造器模式的不足,用到哪个参数就设置哪个参数。创建实例也很容易,也方便阅读。
但是,有个问题,如果我们要把这个类做成不可变类,显然是不可能了。为什么?构造器已经公有化了。另外,在构建过程中JavaBean有可能会一直处于不一致的状态。为什么?因为在用setter不停的赋值,所以我们还要保证其线程安全。
2.3 Builder构建器
Builder构建器,它既能保证像重叠构造器模式那样的安全性,也能保证像JavaBean模式那么好的可读性。这就是建造者模式的一种形式 ,它不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个 builder 对象。然后,客户端在 bulder 对象上调用类似setter的方法,来设置每个相关的可选参数。最后,客户端调用无参 build 方法来生成通常是不可变的对象。
举个创建例子:
public class TT {
public static void main(String[] args){
//测试
WorkParams workParams = new WorkParams.Builder("11005517", 2)
.amount(BigDecimal.TEN)
.rountId(110)
.startBranchCode("11992212")
.stationId(1)
.build();
System.out.println(workParams);
}
}
/**
* 工作流参数
*/
class WorkParams{
//起始机构
private String startBranchCode;
//当前审批机构
private String currentBranchCode;
//审批路线
private int rountId;
//当前站点
private int stationId;
//当前审批角色
private int roleId;
//审批金额
private BigDecimal amount;
public static class Builder{
//起始机构
private String startBranchCode;
//当前审批机构
private String currentBranchCode;
//审批路线
private int rountId;
//当前站点
private int stationId;
//当前审批角色
private int roleId;
//审批金额
private BigDecimal amount;
/**
* 构建器
* @param currentBranchCode
* @param roleId
*/
public Builder(String currentBranchCode, int roleId) {
this.currentBranchCode = currentBranchCode;
this.roleId = roleId;
}
/**
* 初始化WorkParams
* @return
*/
public WorkParams build(){
return new WorkParams(this);
}
public Builder startBranchCode(String startBranchCode) {
this.startBranchCode = startBranchCode;
return this;
}
public Builder stationId(int stationId) {
this.stationId = stationId;
return this;
}
public Builder rountId(int rountId) {
this.rountId = rountId;
return this;
}
public Builder amount(BigDecimal amount) {
this.amount = amount;
return this;
}
}
/**
* 构造器私有化
* @param builder
*/
private WorkParams(Builder builder) {
this.startBranchCode = builder.startBranchCode;
this.currentBranchCode = builder.currentBranchCode;
this.rountId = builder.rountId;
this.stationId = builder.stationId;
this.roleId = builder.roleId;
this.amount = builder.amount;
}
@Override
public String toString() {
return "WorkParams{" +
"startBranchCode='" + startBranchCode + '\'' +
", currentBranchCode='" + currentBranchCode + '\'' +
", rountId=" + rountId +
", stationId=" + stationId +
", roleId=" + roleId +
", amount=" + amount +
'}';
}
}
三、总结
当类的构造器或者静态工厂方法创建对象时中具有多个参数时,Builder构建器模式是一个不错的选择, 特别是当大多数参数都是可选或者类型相同的时候,使用 Builder 模式的客户端代码将更易于阅读和编写,而且构建过程中也比较安全。相应的随着参数的增加Builder构建器模式的代码量也相应的增加了,而且在构建上也比其它两种相对复杂一下,但这并不妨碍它的闪光点。