场景:当一个类的构造行数或者静态工厂方法(出门右转)拥有多个传参并且会根据传参的数量生成不同对象的时候,往往容易写出难以阅读和维护的代码,比如:
public class Demo {
private int p1;
private String p2;
private boolean p3;
...
public Demo(int p1) {
this(p1, null);
}
public Demo(int p1, String p2) {
this(p1, p2, false);
}
public Demo(int p1, String p2, boolean p3) {
this(...);
}
...
public Demo(int p1, String p2, boolean p3, .......) {
this.p1 = p1;
this.p2 = p2;
...
}
}
使用静态工厂方法可以通过命名区分不同的实例创建情况,可以一定程度上避免阅读性差、使用成本高的问题,但是解决不了传参过多的问题,同时也会造成一个难题,就是需要开发者需要想出比较好的、能够区分各种实例创建情况的名称
这种写法不利于扩展,阅读性也比较差,使用者只能通过一个个查看变量名称去判断怎么使用,同时,还有一个风险,那就是如果使用者传错了同种类型的参数,或者传错了同种类型的参数的顺序,编译器是不会发现的,但是会在程序运行时导致功能异常。
另外一种方式是使用JavaBeans的模式,也就是只提供一个不带参数的构造函数,然后通过setter一个个设置,提高了可阅读性,但是却不能保证对象信息在多线程情况下的同步,因为setter方法不具备原子性的。
这种情况下应该考虑使用Builder模式来代替。Builder模式应该都不陌生,这里简单提几个要点:
1.Builder类一般为宿主类的内部静态类,这样可以实现懒加载。
2.宿主类和Builder的数据传递就是Builder的对象,也就是说需要提供一个传参为该类的Builder类的对象的构造器。
3.Builder类的每个setter方法返回builder对象自己,同时需要增加校验操作,比如setMoney(float num)方法,当检查到传入的值<0时抛出IllegalArgumentException。
4.Builder模式也可以通过设计一个抽象的父类去约束项目中该父类子类的Builder规范以及提供一些子类通用的操作,而子类还可以继续扩充setter,看一下例子会更好懂:
public abstract class BaseClass {
protected final List<String> baseList; //子类通用数据
public abstract static class Builder<T extends Builder<T>> {
protected List<String> baseList = new ArrayList();
public T addItem(String item) {
baseList.add(item);
return childInstance();
}
protected abstract T childInstance(); //返回具体子builder实例
public abstract BaseClass build();
}
public BaseClass(Builder<?> builder) { //这里只能用?而不能用T,因为T是Builder的,使用?的话无论Builder的泛型是什么具体的Builder类都能传入
baseList = builder.baseList;
}
}
//子类的builder就可以不写addItem方法,同时可以继续扩展setter
public class ChildClass extends BaseClass {
private int newParam;
public static class Builder extends BaseClass.Builder<Builder> {
private int newParam;
public Builder() {} //这里可以扩展传参,或者初始化某些变量
@Override
public Builder childInstance() {
return this;
}
@Override
public ChildClass build() {
return new ChildClass(this);
}
public Builder setNewParam(int newParam) {
this.newParam = newParam;
return this;
}
}
private ChildClass(Builder builder) {
super(builder);
newParam = builder.newParam;
}
}
...
//用法
new ChildClass.Builder().addItem("test").setNewParam(23).build();
特别说明:
1.这里的Builder<T extends Builder<T>>的作用是确保传进来T必须是Builder的子类,如果Builder<T extends Builder<T>>换成Builder<T >的写法,完全可以随意传入一个非Builder的子类作为类型参数,比如有个类Demo,Builder<T >的写法可以传Demo作为T的泛型值,这样addItem的返回值就不是子Builder类了,Builder模式就错了,你就不能使用.addItem(…).setNewParam(…)了,假如是这种写法Builder<T extends Builder>,你可以传入另外一个也继承父Builder的子类,同样可能出错,而如果是Builder<T extends Builder<T>>的写法,传入的泛型值后生成的子Builder<T>不是父Builder<T>的子类型就会编译报错了,此时子Builder和父Builder的T一定是一致的。这种写法叫递归类型参数。
2.内部静态类Builder和外部类BaseClass其实没有什么强联系,它们都可以作为独立的类去使用,所以在子类继承BaseClass时,它不一定要写一个内部静态类Builder(可以试一下)。
3.addItem方法的返回值可能会有人第一感官想返回一个T,T是泛型,是类级别的,addItem需要返回一个具体的对象。