# 《Java极简设计模式》第04章:建造者模式(Builder)

  • 本章难度:★★☆☆☆
  • 本章重点:用最简短的篇幅介绍建造者模式最核心的知识,理解建造者模式的设计精髓,并能够灵活运用到实际项目中,编写可维护的代码。

# 一、概述

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

# 二、适用性

1.当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。

2.当构造过程必须允许被构造的对象有不同的表示时。

# 三、参与者

1.Builder 为创建一个Product对象的各个部件指定抽象接口。

2.ConcreteBuilder 实现Builder的接口以构造和装配该产品的各个部件。 定义并明确它所创建的表示。 提供一个检索产品的接口。

3.Director 构造一个使用Builder接口的对象。

4.Product 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。 包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

# 四、类图


# 五、示例一

Builder

/**
 * @author binghe(微信 : hacker_binghe)
 * @version 1.0.0
 * @description Person对象的构造接口
 * @github https://github.com/binghe001
 * @copyright 公众号: 冰河技术
 */
public interface PersonBuilder {

    void buildHead();

    void buildBody();

    void buildFoot();

    Person buildPerson();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

ConcreteBuilder

/**
 * @author binghe(微信 : hacker_binghe)
 * @version 1.0.0
 * @description Person对象的构造器
 * @github https://github.com/binghe001
 * @copyright 公众号: 冰河技术
 */
public class ManBuilder implements PersonBuilder{
    Person person;

    public ManBuilder() {
        person = new Man();
    }

    @Override
    public void buildBody() {
        person.setBody("建造男人的身体");
    }

    @Override
    public void buildFoot() {
        person.setFoot("建造男人的脚");
    }

    @Override
    public void buildHead() {
        person.setHead("建造男人的头");
    }

    @Override
    public Person buildPerson() {
        return person;
    }
}
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

Director

/**
 * @author binghe(微信 : hacker_binghe)
 * @version 1.0.0
 * @description Person对象的整体构造器
 * @github https://github.com/binghe001
 * @copyright 公众号: 冰河技术
 */
public class PersonDirector {

    public Person constructPerson(PersonBuilder pb) {
        pb.buildHead();
        pb.buildBody();
        pb.buildFoot();
        return pb.buildPerson();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Product

/**
 * @author binghe(微信 : hacker_binghe)
 * @version 1.0.0
 * @description Person对象
 * @github https://github.com/binghe001
 * @copyright 公众号: 冰河技术
 */
public class Person {
    private String head;
    private String body;
    private String foot;

    public String getHead() {
        return head;
    }

    public void setHead(String head) {
        this.head = head;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getFoot() {
        return foot;
    }

    public void setFoot(String foot) {
        this.foot = foot;
    }
}
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
/**
 * @author binghe(微信 : hacker_binghe)
 * @version 1.0.0
 * @description 创建一个男人类继承Person
 * @github https://github.com/binghe001
 * @copyright 公众号: 冰河技术
 */
public class Man extends Person{
}
1
2
3
4
5
6
7
8
9

Test

package com.lyz.design.builder;

/**
 * 测试类
 * @author liuyazhuang
 *
 */
public class Test {
	public static void main(String[] args) {
		PersonDirector pd = new PersonDirector();
		Person person = pd.constructPerson(new ManBuilder());
		System.out.println(person.getBody());
		System.out.println(person.getFoot());
		System.out.println(person.getHead());
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

result

建造男人的头
建造男人的身体
建造男人的脚
1
2
3

# 六、示例二

假设我们要⾃⼰开发⼀个RabbitMQ消息队列的客户端,有很多需要初始化的参数,你会怎么做?

/**
 * 基于构造⽅法为属性赋值⽆法适⽤于灵活多变的环境,且参数太⻓很难使⽤
 */
public class RabbitMQClientSample1 {
 private String host = "127.0.0.1";
 private int port = 5672;
 private int mode;
 private String exchange;
 private String queue;
 private boolean isDurable = true;
 int connectionTimeout = 1000;
 private RabbitMQClientSample1(String host, int port , int mode, String
exchange , String queue , boolean isDurable, int connectionTimeout){
     this.host = host;
     this.port = port;
     this.mode = mode;
     this.exchange = exchange;
     this.queue = queue;
     this.isDurable = isDurable;
     this.connectionTimeout = connectionTimeout;
     if(mode == 1){ //⼯作队列模式不需要设置交换机,但queue必填
             if(exchange != null){
                 throw new RuntimeException("⼯作队列模式⽆须设计交换机");
             }
             if(queue == null || queue.trim().equals("")){
                 throw new RuntimeException("⼯作队列模式必须设置队列名称");
             }
             if(isDurable == false){
                 throw new RuntimeException("⼯作队列模式必须开启数据持久化");
             }
     }else if(mode ==2){ //路由模式必须设置交换机,但不能设置queue队列
             if(exchange == null || exchange.trim().equals("")){
                 throw new RuntimeException("路由模式请设置交换机");
             }
             if(queue != null){
                 throw new RuntimeException("路由模式⽆须设置队列名称");
             }
     }
     //其他各种验证
     }
    
 public void sendMessage(String msg) {
 System.out.println("正在发送消息:" + msg);
 }
    
//使用方式    
public static void main(String[] args) {
 //⾯对这么多参数恶不恶⼼?
 RabbitMQClientSample1 client = 
     new RabbitMQClientSample1("192.168.31.210", 5672, 2, "sample-exchange", null, true, 5000);
 client.sendMessage("Test");
 }
}
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

我的天,这么多参数要死啊,还是改⽤set⽅法灵活赋值吧

public class RabbitMQClientSample2 {
     private String host = "127.0.0.1";
     private int port = 5672;
     private int mode;
     private String exchange;
     private String queue;
     private boolean isDurable = true;
     int connectionTimeout = 20;
    
 //让对象不可变
 private RabbitMQClientSample2(){
 }
    
 public String getHost() {
 return host;
 }
    
 public void setHost(String host) {
 this.host = host;
 }
    
 public int getPort() {
 return port;
 }
    
 public void setPort(int port) {
 this.port = port;
 }
    
 public int getMode() {
 return mode;
 }
    
 public void setMode(int mode) {
 this.mode = mode;
 }
    
 public String getExchange() {
 return exchange;
 }
    
 public void setExchange(String exchange) {
 if(mode == 1){ //⼯作队列模式不需要设置交换机,但queue必填
         if(exchange != null){
        throw new RuntimeException("⼯作队列模式⽆须设计交换机");
         }
     if(queue == null || queue.trim().equals("")){
         throw new RuntimeException("⼯作队列模式必须设置队列名称");
     }
     if(isDurable == false){
         throw new RuntimeException("⼯作队列模式必须开启数据持久化");
     }
 }else if(mode ==2){ //路由模式必须设置交换机,但不能设置queue队列
     if(exchange == null || exchange.trim().equals("")){
     throw new RuntimeException("路由模式请设置交换机");
     }
     if(queue != null){
     throw new RuntimeException("路由模式⽆须设置队列名称");
     }
 }
     this.exchange = exchange;
 }
    
 public String getQueue() {
 return queue;
 }
    
 public void setQueue(String queue) {
 if(mode == 1){ //⼯作队列模式不需要设置交换机,但queue必填
     if(exchange != null){
         throw new RuntimeException("⼯作队列模式⽆须设计交换机");
     }
     if(queue == null || queue.trim().equals("")){
         throw new RuntimeException("⼯作队列模式必须设置队列名称");
     }
     if(isDurable == false){
         throw new RuntimeException("⼯作队列模式必须开启数据持久化");
     }
 }else if(mode ==2){ //路由模式必须设置交换机,但不能设置queue队列
     if(exchange == null || exchange.trim().equals("")){
         throw new RuntimeException("路由模式请设置交换机");
     }
     if(queue != null){
         throw new RuntimeException("路由模式⽆须设置队列名称");
     }
 }
 this.queue = queue;
 }
    
 public boolean isDurable() {
 return isDurable;
 }
    
 public void setDurable(boolean durable) {
 isDurable = durable;
 }
    
 public int getConnectionTimeout() {
 return connectionTimeout;
 }
    
 public void setConnectionTimeout(int connectionTimeout) {
 this.connectionTimeout = connectionTimeout;
 }
    
 //没办法,必须增加⼀个额外的validate⽅法验证对象是否复合要求
 public boolean validate(){
 if(mode == 1){ //⼯作队列模式不需要设置交换机,但queue必填
     if(exchange != null){
     throw new RuntimeException("⼯作队列模式⽆须设计交换机");
     }
     if(queue == null || queue.trim().equals("")){
     throw new RuntimeException("⼯作队列模式必须设置队列名称");
     }
     if(isDurable == false){
     throw new RuntimeException("⼯作队列模式必须开启数据持久化");
     }
 }else if(mode ==2){ //路由模式必须设置交换机,但不能设置queue队列
     if(exchange == null || exchange.trim().equals("")){
     throw new RuntimeException("路由模式请设置交换机");
     }
     if(queue != null){
     throw new RuntimeException("路由模式⽆须设置队列名称");
     }
  }
 return true;
 //其他各种验证
 }
    
 public void sendMessage(String msg) {
 System.out.println("正在发送消息:" + msg);
 }
 //调用   
 public static void main(String[] args) {
 RabbitMQClientSample2 client = new RabbitMQClientSample2();
 client.setHost("192.168.31.210");
 client.setMode(1);
 client.setDurable(true);
 client.setQueue("queue");
 client.validate();
 client.sendMessage("Test");
 }
    
}
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

利⽤SET⽅法虽然灵活,但是存在中间状态,且属性校验时有前后顺序约束,或者还需要构建额外的校验⽅法并且SET⽅法破坏了“不可变对象”的密闭性如何保障灵活组织参数,有可以保证不会存在中间状态以及基本信息不会对外泄露呢?

建造者模式是⼀个好选择

构建者模式的特点:摆脱超⻓构造⽅法参数的束缚的同时也保护了”不可变对象“的密闭性

建造者模式的格式如下:

  1. ⽬标类的构造⽅法要求传⼊Builder对象
  2. Builder建造者类位于⽬标类内部且⽤static描述
  3. Builder建造者对象提供内置属性与各种set⽅法,注意set⽅法返回Builder对象本身
  4. Builder建造者类提供build()⽅法实现⽬标类对象的创建

Builder

public class ⽬标类(){
 //⽬标类的构造⽅法要求传⼊Builder对象
 public ⽬标类(Builder builder){
 
 }
 
 public 返回值 业务⽅法(参数列表){
 //doSth
 }
 //Builder建造者类位于⽬标类内部且⽤static描述
 public static class Builder(){
 //Builder建造者对象提供内置属性与各种set⽅法,注意set⽅法返回Builder对象本身
 private String xxx ;
 public Builder setXxx(String xxx) {
 this.xxx = xxx;
 return this;
 }
 
 //Builder建造者类提供build()⽅法实现⽬标类对象的创建
 public ⽬标类 build() {
 //业务校验
 return new ⽬标类(this);
 }
 }
}
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
* 建造者模式进⾏优化
 */
public class RabbitMQClient {
    
 private RabbitMQClient(Builder builder){
 }
    
 public void sendMessage(String msg) {
 System.out.println("正在发送消息:" + msg);
 }
    
 public static class Builder {
 private String host = "127.0.0.1";
 private int port = 5672;
 private int mode;
 private String exchange;
 private String queue;
 private boolean isDurable = true;
 private int connectionTimeout = 20;
     
 public Builder setHost(String host) {
 this.host = host;
 return this;
 }
     
 public Builder setPort(int port) {
 this.port = port;
 return this;
 }
     
 public Builder setMode(int mode) {
 this.mode = mode;
 return this;
 }
     
 public Builder setExchange(String exchange) {
 this.exchange = exchange;
 return this;
 }
     
 public Builder setQueue(String queue) {
 this.queue = queue;
 return this;
 }
     
 public Builder setDurable(boolean durable) {
 isDurable = durable;
 return this;
 }
     
 public RabbitMQClient build() {
 if(mode == 1){ //⼯作队列模式不需要设置交换机,但queue必填
     if(exchange != null){
         throw new RuntimeException("⼯作队列模式⽆须设计交换机");
     }
     if(queue == null || queue.trim().equals("")){
         throw new RuntimeException("⼯作队列模式必须设置队列名称");
     }
     if(isDurable == false){
         throw new RuntimeException("⼯作队列模式必须开启数据持久化"
    );
     }
 }else if(mode ==2){ //路由模式必须设置交换机,但不能设置queue队列
     if(exchange == null || exchange.trim().equals("")){
         throw new RuntimeException("路由模式请设置交换机");
     }
     if(queue != null){
         throw new RuntimeException("路由模式⽆须设置队列名称");
     }
     }
     //其他验证
     return new RabbitMQClient(this);
     }
 }
    
 public static void main(String[] args) {
 RabbitMQClient client = new RabbitMQClient.Builder().setHost("192.168.31.201").setMode(2).setExchange("test-exchange").build();
 client.sendMessage("Test");
 }
}
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