前面我们提到使用静态工厂方法代替构造方法,因为静态工厂相对于构造方法有诸多个优势。但静态工厂和构造方法有一个共同的局限性:它们都不能很好地扩展到大量的可选参数。比如我们有一个用于描述字段属性的类,一个字段通常包含:字段名、字段标题、字段是否可见、字段是否必填等等属性,其中字段名、字段标题是必填的,其它都是非必填。
对于这样的类,我们应该怎么设计它。通常来说,大部分的开发者都会想到使用重载的方式来提供几个可能的构造方法保证客户端可以快速的创建出这样的一个字段对象出来。比如这样:
public class Field {
private String name;
private String title;
private boolean visible = true;
private boolean allowBlank = true;
public Field(String name, String title) {
this(name, title, true);
}
public Field(String name, String title, boolean visible) {
this(name, title, visible, true);
}
public Field(String name, String title, boolean visible, boolean allowBlank) {
this.name = name;
this.title = title;
this.visible = visible;
this.allowBlank = allowBlank;
}
}
当我们想要创建一个可见且非必填的字段时,就可以调用最短的构造方法创建对象;当我们想要创建一个不可见且必填的字段时,那我们就得调用最长的构造方法。
我这里只是简单的举了个只包含四个属性的例子,这么看来这种方式还不算很糟糕。但问题是随着属性的增加,它很快就失去了控制。最后我们创建一个对象就不得不这样:
Field field = new Field("text", "单行文本");
field.setVisible(false);
field.setAllowBlank(false);
field.setDefaultValue(xxx);
field.setReadOnly(false);
...
类似上述的写法,我们称之为JavaBeans
模式。这种模式下先创建一个最简单的对象,然后通过调用setter
方法来设置每个参数。这种方式虽然创建对象很容易,写出来的代码阅读起来也很容易。但是它最大的缺点是:该模式使得把类做成不可变类的可能性不复存在。
说了这么多,我们现在就正式进入本篇的主题——建造者模式。建造者模式可以保证像重载的构造方法那样安全,也能像JavaBeans
模式那样可读。通常来说,建造者模式不直接生成最终对象,而是让客户端得到一个builder
对象,然后在builder
对象上调用类似于setter
的方法来设置每个相关的可选参数。最后调用无参的build
方法来生成通常不可变的对象。如下:
public class Field {
private String name;
private String title;
private boolean visible = true;
private boolean allowBlank = true;
private Field(Builder builder) {
this.name = builder.name;
this.title = builder.title;
this.visible = builder.visible;
this.allowBlank = builder.allowBlank;
}
public static class Builder {
private String name;
private String title;
private boolean visible = true;
private boolean allowBlank = true;
public Builder(String name, String title) {
this.name = name;
this.title = title;
}
public Builder visible(boolean visible) {
this.visible = visible;
return this;
}
public Builder allowBlank(boolean allowBlank) {
this.allowBlank = allowBlank;
return this;
}
public Field build() {
return new Field(this);
}
}
}
builder
的设值方法返回builder
本身,以便把调用链接起来,得到一个流式的API。客户端代码:
Field field = new Builder("text", "单行文本").visible(false).allowBlank(false).build();
总结,如果类的构造方法或者静态工厂中具有多个参数,设计这种类时,Builder
模式就是一种不错的选择,特别适当大多数参数都是可选或者类型相同的时候。