概述

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

image-20220815225547077

  • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

结构:

建造者(Builder)模式由抽象建造者、具体建造者、****产品、导向器等 4 个要素构成

preview

一般端直接和Director导向器沟通,通过向Director传入不同的ConcreteBuilder(具体建造者)构建不同表示的Product(产品)。

所以建造者模式把对象的构建(由Builder的实现来负责的)和装配(由Director负责的)进行了解耦,不同的构建器,相同的装配,也可以做出不同的产品。

模式的优缺点

  • 优点

1)可以逐步构建对象、延迟构建步骤,将构建和表示分离;

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//1)抽象建造者
public interface Builder {
void setTyre(Tyre tyre);
void setSeat(Seat seat);
void setEngine(Engine engine);
}

class Tyre{
private String tyreVersion;

public Tyre(){
this.tyreVersion="入门级轮胎,寿命5年";
}
public Tyre(String tyreVersion) {
this.tyreVersion = tyreVersion;
}

public String getTyreVersion() {
return tyreVersion;
}
}

class Seat{
private String SeatVersion;

public Seat(){
this.SeatVersion="普通座椅";
}

public Seat(String seatVersion) {
SeatVersion = seatVersion;
}

public String getSeatVersion() {
return SeatVersion;
}

}

class Engine{
private String EngineVersion;

public Engine(){
this.EngineVersion="普通发动机";
}

public Engine(String engineVersion) {
EngineVersion = engineVersion;
}
public String getEngineVersion() {
return EngineVersion;
}
}

然后再创建实际建造者及产品;

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
//2)实际建造者
public class BydBuilder implements Builder {

private Tyre tyre;
private Seat seat;
private Engine engine;

@Override
public void setEngine(Engine engine) {
this.engine = engine;
}

@Override
public void setSeat(Seat seat) {
this.seat = seat;
}

@Override
public void setTyre(Tyre tyre) {
this.tyre = tyre;
}
//得到产品
public BydCar getMyBydCar(){
return new BydCar(tyre, seat, engine);
}
}

class BydCar{
private Tyre tyre;
private Seat seat;
private Engine engine;

public BydCar(Tyre tyre,Seat seat,Engine engine){
this.tyre = tyre;
this.seat = seat;
this.engine = engine;
System.out.println(this.toString());
}

public Tyre getTyre() {
return tyre;
}
public Seat getSeat() {
return seat;
}
public Engine getEngine() {
return engine;
}

@Override
public String toString() {
return this.engine.getEngineVersion()+"+"+
this.tyre.getTyreVersion()+"+"+
this.seat.getSeatVersion();
}

}

再创建导向器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//3)创建导向器
public class Director {

public void highBydCar(Builder builder){
builder.setEngine(new Engine("高端发动机"));
builder.setTyre(new Tyre("高端轮胎,可用10年"));
builder.setSeat(new Seat("高端真皮座椅"));
}

public void lowBydCar(Builder builder){
builder.setEngine(new Engine());
builder.setTyre(new Tyre());
builder.setSeat(new Seat());
}
//目前预售中端款比亚迪
public void MiddleBydCar(Builder builder){
builder.setEngine(new Engine("高端发动机"));
builder.setTyre(new Tyre()); //低端轮胎
builder.setSeat(new Seat()); //低端座椅
}
}

最后就是我们的客户端调用者。

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 Client {

public static void main(String[] args) {
//导向器
Director director = new Director();
//建造者
BydBuilder builder = new BydBuilder();
//产品
System.out.println("顾客1,看中了高端比亚迪");
director.highBydCar(builder);
builder.getMyBydCar();
System.out.println("顾客2,看中了低端比亚迪");
director.lowBydCar(builder);
builder.getMyBydCar();
System.out.println("顾客3,看中了中端比亚迪");
director.MiddleBydCar(builder);
builder.getMyBydCar();

System.out.println("顾客4,希望按照自己的意愿定制比亚迪");
builder.setEngine(new Engine("高端发动机"));
builder.setSeat(new Seat("高端座椅"));
builder.setTyre(new Tyre());//低端轮胎
builder.getMyBydCar();
}
}

顾客1,看中了高端比亚迪
高端发动机+高端轮胎,可用10年+高端真皮座椅
顾客2,看中了低端比亚迪
普通发动机+入门级轮胎,寿命5年+普通座椅
顾客3,看中了中端比亚迪
高端发动机+入门级轮胎,寿命5年+普通座椅
顾客4,希望按照自己的意愿定制比亚迪
高端发动机+入门级轮胎,寿命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
public class Phone {
private String cpu;
private String screen;
private String memory;
private String mainboard;

public Phone(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}

public String getCpu() {
return cpu;
}

public void setCpu(String cpu) {
this.cpu = cpu;
}

public String getScreen() {
return screen;
}

public void setScreen(String screen) {
this.screen = screen;
}

public String getMemory() {
return memory;
}

public void setMemory(String memory) {
this.memory = memory;
}

public String getMainboard() {
return mainboard;
}

public void setMainboard(String mainboard) {
this.mainboard = mainboard;
}

@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}

public class Client {
public static void main(String[] args) {
//构建Phone对象
Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");
System.out.println(phone);
}
}

上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。

重构后代码:

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
public class Phone {

private String cpu;
private String screen;
private String memory;
private String mainboard;

private Phone(Builder builder) {
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}

public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;

public Builder() {}

public Builder cpu(String val) {
cpu = val;
return this;
}
public Builder screen(String val) {
screen = val;
return this;
}
public Builder memory(String val) {
memory = val;
return this;
}
public Builder mainboard(String val) {
mainboard = val;
return this;
}
public Phone build() {
return new Phone(this);}
}
@Override
public String toString() {
return "Phone{" +
"cpu='" + cpu + '\'' +
", screen='" + screen + '\'' +
", memory='" + memory + '\'' +
", mainboard='" + mainboard + '\'' +
'}';
}
}

public class Client {
public static void main(String[] args) {
Phone phone = new Phone.Builder()
.cpu("intel")
.mainboard("华硕")
.memory("金士顿")
.screen("三星")
.build();
System.out.println(phone);
}
}

重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。从软件设计上,对程序员的要求比较高。

总结及建议

建造者模式实现了构建和装配的解耦,不同的构建器,相同的装配,可以做出不同的产品对象;相同的构建器不同的装配顺序也可以做出不同的产品对象。

应用场景:

  • 相同的方法,不同的执行顺序,产生不同的结果。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
  • 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

DJK中建造者模式的使用:

  • java.lang.StringBuilder#append() (unsynchronized)
  • java.lang.StringBuffer#append() (synchronized)
  • java.nio.ByteBuffer#put()

创建者模式对比

工厂方法模式VS建造者模式

工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

抽象工厂模式VS建造者模式

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。