我们假设用户类有数十个属性,比如:姓名,性别,年龄等等。
如果使用单一的构造器,会造成构造器参数过多的问题。过多的构造器参数不但降低了代码的可读性,而且大大增加了程序员出错的几率。
比较普遍的解决方案有两个:重叠构造器和Java Bean。我们将介绍并分析这两种方式的优劣并在最后给出一种更合理的解决方案。
重叠构造器
重叠构造器的构建首先需要分析所有属性。把属性非为必填和可选属性两类。第一类构造器提供所有的必填属性。第二类构造器的参数除了必填属性外增加一个可选属性,第三类增加两个可选属性,以此类推,直到构造器包含所有的属性。
比如说,我们的用户类只有姓名和性别是必填属性,那么我们可以如下构建重叠构造器(只罗列若干属性):
package com.joshua.code.sample;
public class User {
private String name;
private int sex;
private String nation;
private String city;
...
public User(String name, int sex) {
this(name, sex, "unknown", "unknown");
}
public User(String name, int sex, String nation) {
this(name, sex, nation, "unknown");
}
public User(String name, String city, int sex) {
this(name, sex, "unknown", city);
}
public User(String name, int sex, String nation, String city) {
this.name = name;
this.sex = sex;
this.nation = nation;
this.city = city;
}
}
这个方案能屏蔽一些用户不关心的属性,在一定程度上缓解参数多的问题。细心的读者可能会发现,第二和第三个构造器的参数类型完全相同,在这种情况下,我们只能更换参数的顺序来避免构造器签名的重复,这是重叠构造器的第一大缺陷。此外,代码可读性的问题也没有根本性的解决。
Java Bean
Java bean的实现主要是私有化类的成员变量,并通过getter和setter来访问成员变量。相信大家都会写,IDE也可以自动生成,我就不上代码了。
它的优势在于构造器可以无参,使用setter时,代码的可读性好:
user.setNation("China");
设置属性值的过程一目了然。
缺点在于,构造对象的过程(包括为成员变量赋值)被分到了若干个方法中。这会导致构造过程的Java Bean处于不一致的状态。Java运行时环境不能保证对象在完全构建完成之前(含赋值)不被调用。并且程序员必须花精力来确保这个过程的线程安全性。
Builder
有什么方式可以兼顾重叠构造器的一致性和Java Bean的可读性呢?答案是Builder。我们先来看下用户编程接口:
User user = new User.Builder("Joshua", 0).age(10).city("Shanghai").build();
显而易见,上方的构建方式不但解决了代码可读性的问题并大幅减少了构造器的参数且构建过程保证了一定的一致性。这种具名的参数构建方式酷似Phython等语言。下面我们给出源码:
package com.joshua.code.sample.effective.builder;
public class User {
private String name;
private int age;
private int sex;
private String city;
private String nation;
private long birthday;
public static class Builder {
// required
private final String name;
private final int sex;
// optional
private int age = 0;
private String city = "unknown";
private String nation = "unknown";
private long birthday = 0L;
public Builder(String name, int sex) {
this.name = name;
this.sex = sex;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder city(String city) {
this.city = city;
return this;
}
public Builder nation(String nation) {
this.nation = nation;
return this;
}
public Builder birthday(long birthday) {
this.birthday = birthday;
return this;
}
public User build() {
return new User(this);
}
}
private User(Builder build) {
this.age = build.age;
this.birthday = build.birthday;
this.city = build.city;
this.nation = build.nation;
this.name = build.name;
this.sex = build.sex;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getSex() {
return sex;
}
public String getCity() {
return city;
}
public String getNation() {
return nation;
}
public long getBirthday() {
return birthday;
}
public static void main(String[] args) {
User user = new User.Builder("Joshua", 0).age(10).city("Shanghai").build();
}
}
好的实现方式自然是人见人爱,Builder这种构建模式已经被Apache commons的lang项目吸收。你可以在该jar包内发现一个名为org.apache.commons.lang3.builder.Builder的接口:
public interface Builder<T> {
public T build();
}
当然具体的实现还是要靠大家自己来做。
参考文档:
1. 《Effective Java》
2. Apache commons-lang API