一、定义

建造者模式:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式称为建造者模式。

概念较为复杂,我们还是通过实例需求进行建造者模式的理解。

二、需求

这里顶一个电脑类,并且实例化电脑类的对象,并给出该对象的属性赋值。

三、代码实现

3.1 版本一

通过直接创建出电脑类,并通过客户端进行赋值,创建出该对象,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Computer {
private String cpu;
private String gpu;
private String memery;
private String hd;

// 省略setter getter和toString方法
}

// ========================服务单/客户端=================================

public class AppTest {
public static void main(String[] args) {
Computer c = new Computer();
c.setCpu("i7 7500u");
c.setMemery("16g");
c.setGpu("RTX2080t");
c.setHd("1T机械");
System.out.println(c);
}
}

即:服务端(代码提供者)提供了Computer这个类,具体需要什么样的电脑交给了客户端自己去实现,根据自己的需求自己设定即可。

缺点:

  1. 客户端,在实例化好产品的对象以后,必须为该对象的每一个属性赋值,这样对于客户端来说,太过麻烦。
  2. 违反了迪米特法则(一个类应该对自己依赖的类知道的越少越好,而这里却要自己去进行对象的赋值);

这相当于在现实中,去电脑城买电脑,商家给零件扔给你,你自己回家组装。

3.2 版本二

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
class Computer {
private String cpu;
private String gpu;
private String memery;
private String hd;

// ....
}

// 电脑建造者类,建造者类,必须关联电脑产品
class ComputerBuilder {

private Computer computer = new Computer();

public Computer builder() {
computer.setCpu("i7 7500u");
computer.setMemery("32g");
computer.setGpu("RTX2080t");
computer.setHd("1T机械");
return computer;
}
}

// ========================服务端/客户端================================

public class AppTest {
public static void main(String[] args) {
ComputerBuilder cb = new ComputerBuilder();
Computer computer = cb.builder();
System.out.println(computer);
}
}

目前的优点:

  1. 客户端需要一个产品时,直接向建造者要即可,建造者封装了创建电脑的”复杂”过程。

目前的缺点:

  1. 封装得太死了,无论客户端需求什么样的电脑,都只能采用这一种的配置进行使用。

3.3 版本三

继而改进,针对不同需求,我们需要创建不同的创造者,来分别生产不同配置的产品。

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
class Computer {
private String cpu;
private String gpu;
private String memery;
private String hd;

// ....
}

// 电脑建造者类,建造者类,必须关联电脑产品
class AdviceComputerBuilder {

private Computer computer = new Computer();

public Computer builder() {
computer.setCpu("i7 7500u");
computer.setMemery("32g");
computer.setGpu("RTX2080t");
computer.setHd("1T机械");
return computer;
}
}

class MiddleComputerBuilder {

private Computer computer = new Computer();

public Computer builder() {
computer.setCpu("i7 7500u");
computer.setMemery("8g");
computer.setGpu("RTX1060t");
computer.setHd("1T机械");
return computer;
}
}

class LowComputerBuilder {

private Computer computer = new Computer();

public Computer builder() {
computer.setCpu("i7 7500u");
computer.setMemery("2g");
computer.setGpu("gtx940m");
computer.setHd("500g机械");
return computer;
}
}
// ============================服务端/客户端=============================
public class AppTest {

public static void main(String[] args) {
AdviceComputerBuilder acb = new AdviceComputerBuilder();
MiddleComputerBuilder mid = new MiddleComputerBuilder();
LowComputerBuilder low = new LowComputerBuilder();
// 玩游戏
Computer c = acb.builder();
System.out.println(c);
// 开发
Computer c1 = mid.builder();
System.out.println(c1);
// 办公
Computer c2 = low.builder();
System.out.println(c2);

}
}

这样根据不同的需求,给出不同的建造者类。

优点:

  1. 可以根据客户端的不同需求,使用不同的建造者来生产产品

缺点:

  1. 多个不同的建造者中的代码,在重复!既然代码中出现了重复的代码,那么就能继续优化。
  2. 建造的过程不稳定,如果在某个建造者创建产品时,漏掉了一步,编译器也不会存在报错。(等于在组装电脑的时候少了某一个步骤,原因就是没有标准,这里标准就是要定义接口)

3.4 版本四

继而继续进行优化:

创建一个建造者接口,把制作产品的具体步骤,稳定下来!

我们让建造者类,去实现建造者接口,接口中的方法步骤,类就必须都要实现,少实现一个抽象方法就会报错。

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
class Computer {
private String cpu;
private String gpu;
private String memery;
private String hd;

// ....
}

interface ComputerBuilder {
void setCpu();
void setGpu();
void setMemery();
void setHd();
Computer computer();
}

// 通过接口 过程就会稳定下来
class LowComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void setCpu() {
computer.setCpu("i7 7500u");

}
@Override
public void setGpu() {
computer.setGpu("gtx940m");

}
@Override
public void setMemery() {
computer.setMemery("2g");

}
@Override
public void setHd() {
computer.setHd("500g机械");

}
@Override
public Computer computer() {
return computer;
}
}
/**
省略了上面其他两种电脑的创建
**/

// =========================================================
public class AppTest {
public static void main(String[] args) {
// 只以低配版的电脑建造为例
ComputerBuilder c = new LowComputerBuilder();
c.setCpu();
c.setGpu();
c.setHd();
c.setMemery();
Computer computer = c.computer();
System.out.println(computer);
}
}

目前这种方式,将电脑创建的过程稳定下来,因为我们让具体电脑的类去实现接口,而接口的功能是稳定的,要想使用这个类,必须要实现定义好的接口的全部方法。同时,如果客户端想要扩展电脑的种类,只需要实现ComputerBuilder接口即可,自己定义一个Builder。

优点:

建造者类中的建造过程是稳定的,不会漏掉某一步!当客户端需要扩展建造者时,也不会漏掉一步。

缺点:

  1. 代码仍然存在重复
  2. 现在又变成了客户端自己去配置电脑(在客户端处,又要进行一系列的set方法),又违反了迪米特法则。(相当于去配置电脑,虽然不需要亲自去组装–配置型号等问题,但是必须”指挥”装机者进行装机…)
3.5 最终版本
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
class Computer {
private String cpu;
private String gpu;
private String memery;
private String hd;

// ....
}

interface ComputerBuilder {
void setCpu();
void setGpu();
void setMemery();
void setHd();
Computer computer();
}

class Director { // 作用是将指挥的过程从客户端中分离出来,交给服务端来实现
public Computer build(ComputerBuilder cb) {
cb.setCpu();
cb.setGpu();
cb.setMemery();
cb.setHd();
return cb.computer();
}
}

// 通过接口 过程就会稳定下来
class LowComputerBuilder implements ComputerBuilder {
private Computer computer = new Computer();
@Override
public void setCpu() {
computer.setCpu("i7 7500u");

}
@Override
public void setGpu() {
computer.setGpu("gtx940m");

}
@Override
public void setMemery() {
computer.setMemery("2g");

}
@Override
public void setHd() {
computer.setHd("500g机械");

}
@Override
public Computer computer() {
return computer;
}
}
/**
省略了上面其他两种电脑的创建
**/

// =========================================================
public class AppTest {

public static void main(String[] args) {
// 只以低配版的电脑建造为例
ComputerBuilder c = new LowComputerBuilder(); // 设定需求
Director director = new Director(); // 这里相当于把需求告诉了指挥者,指挥者进行装机过程,该过程多客户端是不可见的,完成了封装
Computer computer = director.build(c);
System.out.println(computer);
}
}

优点:

  1. 创建对象的过程是稳定不变的(因为有ComputerBuilder接口来稳定过程)
  2. 创建对象的过程只写了一次,没有重复代码(指挥者完成)
  3. 当需要扩展指挥者的时候,不需要修改之前的代码,符合了开闭原则。

3.5 类图

image-20210615200256245

Builder完成我们具体的需求,即创建我们所需的对象,但最后由Director指挥者设定具体的过程;它是通过Builder建立对象,Director来将指挥的过程从客户端中分离出来,交给服务端来实现。

四、总结

建造者与工厂模式的区别:

  • 工厂模式只需要一个简单的new,new出产品即可;

  • 建造者更注重,在new出产品之后的,为产品属性赋值的过程;

  • 建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样;

从最后的代码改进中可以看出,builder用来定义对象的属性(定义对象的模板),而Director将对对象进行属性的初始化,给对象进行装配。所以,当需要进行扩展时,只需要实现一个builder类,定义对象的属性模板,然后交给Director去进行初始化,完成对象的封装。

Builder用来定义对象的模板,而Director用于将对象初始化,给对象进行装配。最终达到了,对象的创建和对象的属性装配进行了分离。使得不稳定的创建过程,达到了稳定的效果。


Comment