前言

当一个类的参数多的情况下,使用重叠构造器模式客户端代码会很难编写,并且可读性差;使用javabean模式,调用一个无参的构造器,然后调用setter方法来设置每个必要的参数。但是javabean自身有着严重的缺点,因为构造过程被分到几个调用中,在构造javabean可能处于不一致的状态,类无法仅仅通过检验构造器参数的有效性来保证一致性。另一点不足之处,javabean模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保他的线程安全; build模式 既能保证像重叠构造器那样的安全,也能实现JavaBean模式那样的可读性。
使用build模式的步骤:

  1. 不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个build对象。
  2. 然后让客户端在build对象上调用类似的setter方法来设置每个相关的可选参数,
  3. 最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的静态成员类。

Builder模式

编写对象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class Person{
// 必要参数
// 固定不变的对象,一般变量需要声明为 final
// final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在
private final int id;
private final String name;

// 可选参数
private int age;
private String sex;
private String phone;
private String address;
private String desc;

/**
* 构造方法的参数是它的 静态内部类,使用静态内部类的变量一一赋值
* @param builder
*/
public 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;
}

@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
", desc='" + desc + '\'' +
'}';
}

/**
* Person 的静态内部类,成员变量和 Person 的一致
*/
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;

/**
* 含必选参数的构造方法
* @param name
*/
public Builder(int id, String name) {
this.id = id;
this.name = name;
}

public Builder age(int age) {
this.age = age;
return this;
}

public Builder sex(String sex) {
this.sex = sex;
return this;
}

public Builder phone(String phone) {
this.phone = phone;
return this;
}

public Builder address(String address) {
this.address = address;
return this;
}

public Builder desc(String desc) {
this.desc = desc;
return this;
}

public Person build() {
return new Person(this);
}
}
}

调用

1
2
3
4
5
6
7
8
public static void main(String[] args){
Person person = new Person.Builder(1, "张三")
.age(18)
.sex("男")
.desc("测试使用builder模式")
.build();
System.out.println(person.toString());
}

输出:

1
Person{id=1, name='张三', age=18, sex='男', phone='null', address='null', desc='测试使用builder模式'}

返厂重造

当使用builder构造了一个对象时,如果后期需要对该对象进行属性的修改或者设置其他未设置的属性,可以使用如下方法:

  1. 在Builder类中增加构造方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public Builder(Person person) {
    this.id = person.id;
    this.name = person.name;
    this.age = person.age;
    this.sex = person.sex;
    this.phone = person.phone;
    this.address = person.address;
    this.desc = person.desc;
    }

可以发现,这里与Person对象的构造函数是对称的

  1. 在Person类中增加方法

    1
    2
    3
    public Builder newBuilder() {
    return new Builder(this);
    }
  2. 调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static void main(String[] args) {
    Person personA = new Person.Builder(1, "张三")
    .age(18)
    .sex("男")
    .desc("测试使用builder模式")
    .build();

    Person newPerson = personA.newBuilder()
    .address("new address")
    .desc("测试使用builder模式更改对象属性")
    .build();

    System.out.println(personA.toString());
    System.out.println(newPerson.toString());
    }

输出

1
2
Person{id=1, name='张三', age=18, sex='男', phone='null', address='null', desc='测试使用builder模式'}
Person{id=1, name='张三', age=18, sex='男', phone='null', address='new address', desc='测试使用builder模式更改对象属性'}

注意:这里personA和newPerson是两个不同的对象了

Builder模式在Java中的实例

  1. StringBuilder.append();
  2. Guava Cache

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .removalListener(MY_LISTENER)
    .build(
    new CacheLoader<Key, Graph>() {
    public Graph load(Key key) throws AnyException {
    return createExpensiveGraph(key);
    }
    });
  3. android中okHttp、AlertDialog均有广泛使用

总结

  1. 在要构建的类内部创建一个静态内部类 Builder
  2. 静态内部类的参数与构建类一致
  3. 构建类的构造参数是 静态内部类,使用静态内部类的变量一一赋值给构建类
  4. 静态内部类提供参数的 setter 方法,并且返回值是当前 Builder 对象,从而实现链式调用
  5. 最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象
  6. 返厂重造需求
  7. lombok的@Data等参数提供了自动生成getter、setter、构造函数、toString()等方法,结合实际情况结合使用

Read More

[1] Java高效编程之Builder模式
[2] 变种 Builder 模式:优雅的对象构建方式
[3] 设计模式 Build 模式
[4] Java Builder 模式,你搞懂了么?