一、前景:
一般情况我们不会遇到这样的情况,使用静态工厂方法,或者构造方法就足够。但是它们也有一个限制就是,它们不能很好的扩展到很多可选参数的场景。随着我们业务的深入,某些java bean 中的参数将会越来越多,我们添加的构造方法也相应的增加。想想一个10个参数的构造方法,我胃痛。
既然想要用builder模式,我们首先需要知道传统方法的不足。
二、可伸缩构造方法 VS builder模式
我们都用代码实例来说话
1、可伸缩构造方法
public class Student {
private final String name; // required
private final String sex; // required
private final int weight; // optional
private final int height; // optional
private final int age; // optional
public Student(String name, String sex) {
this(name, sex, 0);
}
public Student(String name, String sex, int w) {
this(name, sex, w, 0);
}
public Student(String name, String sex, int w, int h) {
this(name, sex, w, h, 0);
}
public Student(String name, String sex, int w, int h, int a) {
this.name = name;
this.sex = sex;
this.weight = w;
this.height = h;
this.age = a;
}
}
当我们想创建一个Student实例的时候,可以设置所有参数的最短的够着方法如下:
Student student = new Student("小明", "男", 50, 150, 16);
通常情况下,这个构造方法可能需要许多你不想设置的参数(是不是有点不爽),随着参数的不断增加,它很快会失控的。(你也会发疯的)。首先我们很难读懂这个方法,其次如果反转了两个参数,编译器是不会报错的。比如身高和体重填反了,糟心。。。。。
2、JavaBean模式
当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参数的构造函 数来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数:
public class Student {
private String name; // required
private String sex; // required
private int weight; // optional
private int height; // optional
private int age; // optional
public Student() {}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setHeight(int height) {
this.height = height;
}
public void setAge(int age) {
this.age = age;
}
}
这种模式没有伸缩构造方法的缺点。有点冗长,但创建实例容易,并且容易阅读
Student student = new Student();
student.setName("小明");
student.setSex("男");
student.setAge(16);
student.setWeight(50);
student.setHeight(150);
JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中 JavaBean 可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用 对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类 不可变的可能性,并且需要在程序员的部分增加工作以确保线程安全
3、builder模式
builder 模式结合了可伸缩构造方法模式的安全性和JavaBean模式的可读性。
public class Student {
private final String name;
private final String sex;
private final int weight;
private final int height;
private final int age;
private Student(Builder builder) {
this.name = builder.name;
this.sex = builder.sex;
this.weight = builder.weight;
this.height = builder.height;
this.age = builder.age;
}
public static class Builder {
private final String name; // required
private final String sex; // required
private int weight; // optional
private int height; // optional
private int age; // optional
public Builder(String name, String sex) {
this.name = name;
this.sex = sex;
}
public Builder setWeight(int weight) {
this.weight = weight;
return this;
}
public Builder setHeight(int height) {
this.height = height;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Student build() {
return new Student(this);
}
}
}
我首先来喷一下,代码量翻倍了。但是Student 类是不可变的,所有的参数默认值都在一个地方。builder 的 setter 方法返回 builder 本身, 这样调用就可以被链接起来,从而生成一个流畅的 API。实例如下:
Student student = new Student.Builder("小明", "男").setWeight(50)
.setHeight(150).setAge(16).build();
代码很容易编写,更重要的是易于阅读。 Builder 模式模拟 Python 和 Scala 中的命名可选参数。 这里没有涉及到有效性的检查
总结:
builder 对构造方法的一个微小的优势是,builder 可以有多个可变参数,因为每个参数都是在它自己的方法中指 定的。
Builder 模式非常灵活。 单个 builder 可以重复使用来构建多个对象。 builder 的参数可以在构建方法的调用之间 进行调整,以改变创建的对象。 builder 可以在创建对象时自动填充一些属性,例如每次创建对象时增加的序列号。
Builder 模式也有缺点。为了创建对象,首先必须创建它的 builder。虽然创建这个 builder 的成本在实践中不太可 能被注意到,但在性能关键的情况下可能会出现问题。而且,builder 模式比伸缩构造方法模式更冗长,因此只有在 有足够的参数时才值得使用它,比如四个或更多。但是请记住,如果希望在将来添加更多的参数。但是,如果从构造 方法或静态工厂开始,并切换到 builder,当类演化到参数数量失控的时候,过时的构造方法或静态工厂就会面临尴 尬的处境。因此,所以,最好从一开始就创建一个 builder。
总而言之,当设计类的构造方法或静态工厂的参数超过几个时,Builder 模式是一个不错的选择,特别是如果许 多参数是可选的或相同类型的。客户端代码比使用伸缩构造方法(telescoping constructors)更容易读写,并且 builder 比 JavaBeans 更安全。
ps:如果有微信读书的书友,可以来微信读书群传送门找组织。