建造者模式详解
为什么使用建造者模式(建造者模式的使用场景)
如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合set()方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。
- 我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填属性通过set()方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。
- 如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合set()方法
的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。 - 使用建造者模式创建对象,还能避免对象存在无效状态。比如我们定义了一个长方形类,如果不使用建造者模式,采用先创建后set的方式,那就会导致在第一个set之后,对象处于无效状态。具体代码如下所示:
Rectangle r = new Rectange(); // r is invalid
r.setWidth(2); // r is invalid
r.setHeight(3); // r is valid
如果我们并不是很关心对象是否有短暂的无效状态,也不是太在意对象是否是可变的。比如,对象只是用来映射数据库读出来的数据,那我们直接暴露set()方法来设置类的成员变量值是完全没问题的。而且,使用建造者模式来构建对象,代码实际上是有点重复的,原本的类中的成员变量,要在Builder类中重新再定义一遍。
建造者模式和工厂模式的区别
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
练习题
在下面的ConstructorArg类中,当isRef为true的时候,arg表示String类型的refBeanId,type不需要设置;当isRef为false的时候,arg、type都需要设置。请根据这个需求,完善 ConstructorArg类。
public class ConstructorArg {
private boolean isRef;
private Class type;
private Object arg;
// TODO: 待完善…
}
public class ConstructorArg {
private boolean isRef;
private Class type;
private Object arg;
private ConstructorArg(Builder builder) {
this.isRef = builder.isRef;
this.type = builder.type;
this.arg = builder.arg;
}
public boolean isRef() {
return isRef;
}
public Class getType() {
return type;
}
public Object getArg() {
return arg;
}
// use case
ConstructorArg constructorArg = new ConstructorArg.Builder()
.setRef(true)
.setArg("hello")
.setType(Student.class)
.build();
public static class Builder{
private boolean isRef;
private Class type;
private Object arg;
public ConstructorArg build(){
if(isRef){
if(arg instanceof String){
type = String.class;
}else{
throw new IllegalArgumentException("1");
}
}else{
if(arg == null || type == null){
throw new IllegalArgumentException("2");
}
}
return new ConstructorArg(this);
}
public Builder setRef(boolean ref) {
isRef = ref;
return this;
}
public Builder setType(Class type) {
this.type = type;
return this;
}
public Builder setArg(Object arg) {
this.arg = arg;
return this;
}
}
}