在《Effective Java 第2版》中有提到,遇到多个构造器参数时要考虑使用构建器(Builder模式)。相比于重叠构造器(telescoping constructor)模式和JavaBeans模式,Builder模式实现的对象更利于使用。
下面从一个Person例子进行分析以上三种设计模式的使用,Person类有两个必要参数(id和name),有5个可选参数(age,sex,phone,address和desc)
重叠构造器模式
/**
* 使用重叠构造器模式
*/
public class Person {
//必要参数
private final int id;
private final String name;
//可选参数
private final int age;
private final String sex;
private final String phone;
private final String address;
private final String desc;
public Person(int id, String name) {
this(id, name, 0);
}
public Person(int id, String name, int age) {
this(id, name, age, "");
}
public Person(int id, String name, int age, String sex) {
this(id, name, age, sex, "");
}
public Person(int id, String name, int age, String sex, String phone) {
this(id, name, age, sex, phone, "");
}
public Person(int id, String name, int age, String sex, String phone, String address) {
this(id, name, age, sex, phone, address, "");
}
public Person(int id, String name, int age, String sex, String phone, String address, String desc) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
this.phone = phone;
this.address = address;
this.desc = desc;
}
}
JavaBeans模式
/**
* 使用JavaBeans模式
*/
public class Person {
//必要参数
private int id;
private String name;
//可选参数
private int age;
private String sex;
private String phone;
private String address;
private String desc;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setAddress(String address) {
this.address = address;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
这种模式弥补了重叠构造器模式的不足。创建实例很容易,这样产生的代码读起来也很容易:
Person person = new Person();
person.setId(1);
person.setName("李四");
person.setAge(20);
person.setSex("男");
person.setPhone("18800000000");
person.setAddress("China");
person.setDesc("测试使用JavaBeans模式");
Builder模式(推荐)
幸运的是,还有第三种替代方法,既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。这就是Builder模式的一种形式,不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的builder方法来生成不可变的对象。这个builder是它构建类的静态成员类。下面就是它的示例:
/**
* 使用Builder模式
*/
public class Person {
//必要参数
private final int id;
private final String name;
//可选参数
private final int age;
private final String sex;
private final String phone;
private final String address;
private final String desc;
private Person(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.age = builder.age;
this.sex = builder.sex;
this.phone = builder.phone;
this.address = builder.address;
this.desc = builder.desc;
}
public static class Builder {
//必要参数
private final int id;
private final String name;
//可选参数
private int age;
private String sex;
private String phone;
private String address;
private String desc;
public Builder(int id, String name) {
this.id = id;
this.name = name;
}
public Builder age(int val) {
this.age = val;
return this;
}
public Builder sex(String val) {
this.sex = val;
return this;
}
public Builder phone(String val) {
this.phone = val;
return this;
}
public Builder address(String val) {
this.address = val;
return this;
}
public Builder desc(String val) {
this.desc = val;
return this;
}
public Person build() {
return new Person(this);
}
}
}
注意Person是不可变得,所有的默认参数值都单独放在一个地方。builder的setter方法返回builder本身。以便可以把连接起来。下面是客户端使用代码:
/**
* 测试使用
*/
public class Test {
public static void main(String[] args) {
Person person = new Person.Builder(1, "张三")
.age(18).sex("男").desc("测试使用builder模式").build();
System.out.println(person.toString());
}
}
开源框架的例子
- Android系统对话框AlterDialog的使用,语句如下
//弹出一个对话框
//1.创建builder对象
AlertDialog.Builder b = new AlertDialog.Builder(MainActivity.this)
.setTitle("提示")
.setMessage("哈哈哈")
.setNegativeButton("确定", null)
.setPositiveButton("取消", null)
.create()
.show();//show
- 网络请求封装OkHttp的请求封装,语句如下
private Request(Builder builder){
this.uri=builder.uri;
this.method=builder.method;
this.headers=builder.headers.build();
this.body=builder.body;
this.tag=builder.tag != null ? builder.tag : this;
}