一般情况我们不会遇到这样的情况,使用静态工厂方法,或者构造方法就足够。但是它们也有一个限制就是,它们不能很好的扩展到很多可选参数的场景。随着我们业务的深入,某些java bean 中的参数将会越来越多,我们添加的构造方法也相应的增加。想想一个10个参数的构造方法,我胃痛。
既然想要用builder模式,我们首先需要知道传统方法的不足。
我们都用代码实例来说话
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);
通常情况下,这个构造方法可能需要许多你不想设置的参数(是不是有点不爽),随着参数的不断增加,它很快会失控的。(你也会发疯的)。 首先我们很难读懂这个方法,其次如果反转了两个参数,编译器是不会报错的 。比如身高和体重填反了,糟心。。。。。
当在构造方法中遇到许多可选参数时,另一种选择是 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 模式排除了让类 不可变的可能性,并且需要在程序员的部分增加工作以确保线程安全
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:如果有微信读书的书友,可以来微信读书群传送门找组织。