今天要学习的课题来源于《Effective Java》的第二条:
第二条:遇到多个构造器参数时要考虑使用构建器(Builder)
场景介绍
在一个类中,有几个属性字段是必需的,还有大量的可选字段。在创建这个类的对象时,只会有几个可选字段需要填充。
三种解决方案
作者给出了重叠构造器模式、JavaBeans模式以及建造者(Builder)模式三种可选的解决方案:
重叠构造器模式
提供的第一个构造器只有必要的参数,第二个构造器加上一个可选参数,第三个构造器加上两个可选参数,以此类推…
这种模式下,如果选择的参数并不是连续的,比如说我们只需要构造arg1和arg3的参数,只能调用构造器 Constructor(arg1, arg2, arg3) ,这个arg2还是需要传递值。并且在多个参数类型相同的情况下,这种构造方法很容易造成混乱。
JavaBeans模式
先调用无参构造器把对象构建出来,再通过调用setter方法一个一个设置必要的参数。
这种模式在实际开发中也是比较常用的,弥补了重叠构造器模式的不足,并且提供了很高的可读性。
但是这种模式的缺点在于:对象可能处于不完整状态,缺乏线程安全性:在JavaBeans模式中,使用无参构造器首先创建一个空的对象,然后通过多个setter方法逐步设置对象的属性。在这个过程中,对象在每次调用setter之前都是不完整的,可能存在未设置的必要属性。这意味着在对象完全构建之前,如果它被其他线程访问或使用,可能会导致不一致或不正确的行为。(这里联想到单例模式中用volatile禁止指令重排序,同样是防止其他线程访问到未完全构建的单例)
建造者模式
在 builder.build()
的时候一次性创建完整对象。既能保证像重叠构造器模式的安全性,又具备JavaBeans模式的优秀可读性。
Builder通常是类的静态成员类:
- 具备该类的所有字段;
- 提供一个构造方法,内部可以设置必需参数,从而保证了强制依赖设置;
- 用
new Class.Builder()
来创建该类的Builder对象
- 用
- 设值方法return设值后的builder本身,从而可以将调用链接起来,得到一个十分易读的流式API;
- 提供
build()
方法,调用类中的构造器Constructor(Builder builder)
返回对象。
Builder十分灵活,可以利用一个builder来构建出多个对象(一个 builder 可以多次build()
)
Lombok @Builder
开发中常用Lombok提供的@Builder
注解,为被标注类自动创建相应的Builder静态成员,可以通过将属性标记为 @NonNull 来确保在构建对象时这些属性必须被设置。
使用案例
- mybatis 中封装 Mapper.xml 每条映射的解析结果到 MappedStatement 时,每个参数并不是必须的,这里就用到了建造者模式。像下面的参数:
private MappedStatement(Builder builder) {
this.configuration = builder.configuration;
this.id = builder.id;
this.sqlCommandType = builder.sqlCommandType;
this.parameterType = builder.parameterType;
this.resultType = builder.resultType;
this.sql = builder.sql;
this.parameter = builder.parameter;
}
/**
* 建造者模式
* 不是每个字段都是必须的
*/
public static class Builder {
// 必选参数
private final Configuration configuration;
private final String id;
private final SqlCommandType sqlCommandType;
// 可选
private String parameterType;
private String resultType;
private String sql;
private Map<Integer, String> parameter;
// 必选参数构造
public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType) {
this.configuration = configuration;
this.id = id;
this.sqlCommandType = sqlCommandType;
}
public Builder setParameterType(String parameterType) {
this.parameterType = parameterType;
return this;
}
public Builder setResultType(String resultType) {
this.resultType = resultType;
return this;
}
public Builder setSql(String sql) {
this.sql = sql;
return this;
}
public Builder setParameter(Map<Integer, String> parameter) {
this.parameter = parameter;
return this;
}
public MappedStatement build() {
// 必选参数非空示警
assert this.configuration != null;
assert this.id != null;
return new MappedStatement(this);
}
}
小结
总之,当类的构造器或者静态工厂方法具有大量可选参数时,可以积极考虑使用Builder模式,提供了安全的构造方式(一步构造)以及优秀的可读性(具名构造)。