在面向对象的编程中对象的创建是最基本的动作,但是创建对象的方法有很多种,但是归根结底都是直接或者间接使用类的构造器完成实例的创建,包括静态工厂方法、JavaBean方式或者下面的要说的构建器模式,但是对于不同的情况,使用这几种方法各有利弊,这里使用一个实际的问题来引出这种对比。
【实际需求】
对一个包含10几个参数的类进行实例化,其中有些参数不是必须的,但是有些参数必须存在。
【问题分析】
首先对于实例化的这个动作,对于有编程经验的人再简单不过,第一个直接的想法就是写一个包含所有参数的构造器,但是回想一下这样真的好吗?每次初始化类的时候都会把自己弄晕,这个参数究竟是什么意思?
方法一:重载构造器
接触过Exception类构造器写法的人可能选择重载多个构造器的方法,如下
然后通过构造器之间的调用来完成初始化,但是当面对大量的实例参数时,需要扩展很多的构造器,显然这种方式的扩展性很差。
方法二:使用JavaBean方式
这种方式其实是不通过构造器传递参数,而是通过setter方法,如下:
public class User {
private Integer userId;
private String userName;
private String userPwd;
private String userFullName;
private Date createDate;
public void setUserId(Integer userId) {
this.userId = userId;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserPwd(String userPwd) {
this.userPwd = userPwd;
}
public void setUserFullName(String userFullName) {
this.userFullName = userFullName;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
}
这种方式是我们最熟悉的,在我了解构建器模式之前一直使用的就是这种方式,因为这种方式对于编程者是友好的, 但是在使用的过程中,我就有过一些怀疑,如果调用10个参数的setter方法来实例化该类,但是出于一些原因,这10个调用中只执行了5个,那么这个类怎么办?这不是就出问题了吗?尤其是在多线程的环境下,任何对setter方法的调用都可能出错。
这也是《Effective Java》中所说的会使JavaBean出于不一致的状态的原因,而且还需要使用额外的手段来保证线程安全,因为JavaBean实例是可变的类。
方法三:使用构建器(Builder) 模式
这种方法对编程者非常友好,但是同时也能解决JavaBean对象面向多线程环境的固有缺陷,提高安全性。它是通过以下几种方式完成的:
- 为了解决安全性问题,使用final限制属性不可变并移除setter方法等;
- 为了解决可读性和扩展性问题,通过使用静态嵌套类,在其中设置可变参数方法等;
具体的实现看如下实例。
【具体实现】
public class User {
// 所有属性为final, 保证该类为不可变类
// 在构造器实例化的时候完成,不能在方法中完成
private final String userName;
private final String userPwd;
private final String userFullName;
private final Date createDate;
// 构造器是私有的,确保类实例化只能通过builder模式
// 并且不提供setter方法
private User(Builder builder){
this.userName = builder.userName;
this.userPwd =builder.userPwd;
this.userFullName =builder.userFullName;
this.createDate =builder.createDate;
}
// 静态嵌套类,不能直接访问User的非静态变量,保证安全
public static class Builder{
// 必选参数为final, 在实例化Builder类的时候必须提供
// 而不能由方法来完成
private final String userName;
private final String userPwd;
// 可选参数可以提供默认参数
private String userFullName = "default";
private Date createDate = new Date();
// 包括全部必选参数,它们初始化必须提供参数
public Builder(String userName, String userPwd){
this.userName = userName;
this.userPwd = userPwd;
}
// 可选参数方法, 提高扩展性
public Builder userFullName(String userFullName){
this.userFullName = userFullName;
return this;
}
public Builder createDate(Date createDate){
this.createDate = createDate;
return this;
}
// 实例化外部参数
public User builder(){
return new User(this);
}
}
}
在外部使用的时候,实例化如下:
public static void main( String[] args ){
User.Builder builder = new User.Builder("lmy86263", "123");
User user = builder.userFullName("lmy86263").createDate(new Date()).builder();
}
使用这种方式也可以灵活地调整类实例化的步骤,使类的实例化更有弹性,而且可以向用户隐藏内部的具体实现。