静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。考虑一个类的构造器具有多个参数时,就显得很不方便。
对于这样的类,应该用哪种构造器或者静态方法来编写呢?程序员一般会采用重叠构造器模式,在这种模式下,你提供一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,依次类推,最后一个构造器包含所有的可选参数。下面我们来看下这种示例:
重叠构造器模式
1 | public class Person { |
当我们想要创建实例的时候,就利用各个不同参数的构造器,但是假如我们需要实例化一个最长参数的构造器,它就会变成这样:
1 | Person person = new Person("Bob", 19, 1.85, 76.5, "温和", "旅行"); |
这个构造器调用通常有许多你本不想设置的参数,但还是不得不为它们传递值。重叠构造器模式虽然在参数少的情况下可行,但随着参数数目的增加,我们通常很难去编写,并且也不易阅读。如果用户想知道传参值的含义,必须仔细地数着这些参数来探个究竟。如果用户不小心颠倒了其中两个参数的顺序,编译器可能不会出错,但是在程序运行的时候也会发生致命的错误。
当我们遇到这种情况时,我们还有第二种代替的办法,即JavaBeans
模式。在这种模式下调用一个无参构造器来实例化对象,然后调用setter
方法来设置每个所需要的参数:
JavaBeans模式
1 | public class Person { |
这种模式弥补了重叠构造器模式的不足。创建实例很容易,这样产生的代码读起来也很容易:
1 | Person person = new Person(); |
遗憾的是,JavaBeans
模式自身有着很严重的缺点。因为构造过程被分到了几个调用中,在构造过程中JavaBean
可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败,这种失败与包含错误的代码大相径庭,因此调试起来十分困难。另外,JavaBeans
模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。
所以,我们还有第三种替代方法,技能保证像重叠构造器那样的安全性,也能保证JavaBeans
模式的可读性,这就是Builder
模式。不直接生成想要的对象,而是让客户端在builer
对象上调用类似于etter
的方法,来设置每个相关的可选参数。最后,客户端调用无参的build
方法来生成不可变的对象。这个builder
是它构建的类的静态成员类。如下所示:
构建器模式
1 | public class Person { |
这里注意Person
类是不可变的,所有的默认参数值都单独放在一个地方。builder
的setter
方法返回builder
本身,以便可以把调用链接起来。下面看下调用代码:
1 | Person person = Person.newBuilder("Bob", 19) |
这样的客户端代码很容易编写和易于阅读,更为重要的是它也可以是安全的。uilder
像个构造器一样,可以对其参数强加约束条件。build
方法可以检验这些约束条件,将参数从builder
拷贝到对象中之后,并在对象域而不是builder
域中对它们进行检验,这一点很重要,。如果违反了任何的约束条件,build方
法就应该抛出参数异常错误(IllegalStateException),异常的信息显示出违反了哪个约束条件。
对多个参数强加约束条件的另一种方法是,用多个setter
方法对某个约束条件必须持有的所有参数进行检查。如果没有满足约束条件,setter
方法就抛出IllegalArgumentException
。这样一旦发现了错误,立即发现异常,而不是等着调用build
方法。
设置了参数的builder
生成了一个很好的抽象工厂。客户端可以将这样的一个builder
传给方法,使该方法为客户端创建一个或多个对象。要使用这种方法,我们需要有个类型来表示builder,只需要一个泛型即可满足构建的所有对象的类型:
1 | public interface BuilderFactory<T> { |
接着我们就可以声明Person
的Builder
类来实现BuilderFactory<Person>
。
1 | public static class Builder implements BuilderFactory<Person> { |
Builder模式的确也有自身不足的地方。为了创建对象,必须先创建它的构造器。虽然创建构建器的开销在实践中可能不那么明显,但是在十分注重性能的情况下可能就是一个问题了。Builder
模式比重叠构造器模式更加冗长,因此它只有在很多参数的时候才会去使用,比如4个以上的参数。
简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择。