目录

[TOC]

参考

什么是建造者模式

建造者模式是一种创建型设计模式,它可以将对象的构建与表示分离开来,以便相同的构建过程可以创建不同的表示形式。它允许您创建复杂的对象,同时对其构建过程进行细粒度的控制。

在该模式中,一个Director对象负责根据用户输入指示生成正确的产品。该模式通常用于处理复杂的对象,例如包含多个子组件以及需要执行许多步骤的对象。建造者模式根据建造过程不同分为标准建造者模式和流式建造者模式。在标准建造者模式中,我们使用多个方法来设置建造步骤,在流式建造者模式中,我们使用实现链式调用的单个方法来设置步骤。

四个角色

image-20230608205738300

Product(产品角色): 一个具体的产品对象。

Builder(抽象建造者): 创建一个Product对象的各个部件指定的抽象接口。

ConcreteBuilder(具体建造者): 实现抽象接口,构建和装配各个部件。

Director(指挥者): 构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

拿其中两个套餐举例

套餐A:汉堡,薯条,大鸡腿
套餐B:汉堡,薯条,小鸡腿,小可乐,小披萨

其中薯条和汉堡可大可小,并且必须有,
其它的都为固定大小,但是你可以选择有或没有

使用场景

当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式

这个就非常重要了,因为如果你学了个东西,都不知道用来解决什么问题,你说有什么用?理解使用场景的的重要性要远高于你是不是会实现这个模式,因为只要你知道什么问题可以使用builder模式来解决,那你即使不会写,也可以在调查相关资料后完成。
我不想说一些大而正确的术语来把你搞蒙,我们只针对具体的问题,至于延展性的思考,随着你知识的增长,逐渐会明白的。

代码

KFC套餐案例:假如目前KFC里面有很多个套餐

  • 在套餐里面有必点,也有选点,然后每个单品又有大小之分
  • 必点:汉堡(hamburger),薯条(chips)
  • 选点:鸡腿(chicken),可乐(cola),披萨(pizza)

我们如何构成这么多套餐实例呢?

我们不使用建造者模式也能构建代码,但是建造者模式会让代码看上去更装逼,代码到后期更结构化更容易维护和拓展

首先构建这个实体类`KFC

实体类KFC

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 实体类`KFC
*/
public class KFC {
//套餐必点
private String hamburger;
private String chips;

//套餐选点
private String chicken;
private String cola;
private String pizza;
}

非建造者模式代码

我们的想法是不是折叠构造函数来创建实例,下面来尝试一下

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
/**
* 实体类`KFC
*/
public class KFC {
//套餐必点
private String hamburger;
private String chips;

//套餐选点
private String chicken;
private String cola;
private String pizza;


//必点套餐A
public KFC(String hamburger, String chips) {
this(hamburger, chips, null, null, null);
}

//套餐B
public KFC(String hamburger, String chips, String chicken) {
this(hamburger, chips, chicken, null, null);
}

//套餐C
public KFC(String hamburger, String chips, String chicken, String cola) {
this(hamburger, chips, chicken, cola, null);
}

//......还有好多种组合方式,你会发现使用折叠构造函数的方法十分复杂

//全选
public KFC(String hamburger, String chips, String chicken, String cola, String pizza) {
this.hamburger = hamburger;
this.chips = chips;
this.chicken = chicken;
this.cola = cola;
this.pizza = pizza;
}
}

我们会发现使用折叠构造函数的方式很复杂,很恶心,代码看都不想看

那么有人会想,我可以使用set方法来创建,我只要一个必点构造就好了,那继续模拟咯

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
/**
* 实体类`KFC
*/
public class KFC {
//套餐必点
private String hamburger;
private String chips;

//套餐选点
private String chicken;
private String cola;
private String pizza;

//必点
public KFC(String hamburger, String chips) {
this.hamburger = hamburger;
this.chips = chips;
}

//set方法
public void setChicken(String chicken) {
this.chicken = chicken;
}

public void setCola(String cola) {
this.cola = cola;
}

public void setPizza(String pizza) {
this.pizza = pizza;
}

//实例化对象,你会发现这种方式就友好很多
public static void main(String[] args) {
KFC kfc = new KFC("大汉堡", "大薯条");
//加小份可乐
kfc.setCola("小可乐");
//加个鸡腿
kfc.setChicken("大鸡腿");
System.out.println(kfc);
}
}

你会发现使用set方式就友好了很多

这个虽然友好了很多,但是也有点小毛病,就是你set太随意了,我可能这个套餐里面没有这个单品,而使用set的人却不知道,造成错误的套餐出现!。

为了解决上面的两种问题:一种设计模式解决一类问题,所以建造者模式就出现了

建造者模式代码

产品

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
/**
* KFC产品
*/
public class KFC {
//套餐必点
private String hamburger;
private String chips;

//套餐选点
private String chicken;
private String cola;
private String pizza;

//必点
public KFC(String hamburger, String chips) {
this.hamburger = hamburger;
this.chips = chips;
}

//set方法
public void setChicken(String chicken) {
this.chicken = chicken;
}

public void setCola(String cola) {
this.cola = cola;
}

public void setPizza(String pizza) {
this.pizza = pizza;
}

@Override
public String toString() {
return "KFC{" +
"hamburger='" + hamburger + '\'' +
", chips='" + chips + '\'' +
", chicken='" + chicken + '\'' +
", cola='" + cola + '\'' +
", pizza='" + pizza + '\'' +
'}';
}
}

Builder

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 定义一个接口,表明需要建造什么,得到什么
*/
public interface Builder {
void setChicken();

void setCola();

void setPizza();

KFC getKFC();
}

套餐A

此时应该注意,这个时候还没有生产套餐,只是定义套餐

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
/**
* 套餐A
*/
public class ConcreteBuilder1 implements Builder {
private KFC kfc;

//这一步非常重要
public ConcreteBuilder1(String hamburger, String chips) {
kfc = new KFC(hamburger, chips);
}

@Override
public void setChicken() {
kfc.setChicken("大鸡腿");
}

@Override
public void setCola() {
kfc.setCola(null);
System.out.println("套餐A里面没有可乐");
}

@Override
public void setPizza() {
kfc.setPizza(null);
System.out.println("套餐A里面没有披萨");
}

@Override
public KFC getKFC() {
return kfc;
}
}

套餐B

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
/**
* 套餐B
*/
public class ConcreteBuilder2 implements Builder {
private KFC kfc;
//这一步非常重要
public ConcreteBuilder2(String hamburger,String chips){
kfc = new KFC(hamburger,chips);
}
@Override
public void setChicken() {
kfc.setChicken("小鸡腿");
}

@Override
public void setCola() {
kfc.setCola("小可乐");
}

@Override
public void setPizza() {
kfc.setPizza("小披萨");
}

@Override
public KFC getKFC() {
return kfc;
}
}

Director

真正的执行者,这里把他当作服务员,此时你像服务员点餐

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 定义一个接口,表明需要建造什么,得到什么
*/
public interface Builder {
void setChicken();

void setCola();

void setPizza();

KFC getKFC();
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BuilderTest {
public static void main(String[] args) {
//套餐A
System.out.println("======套餐A======");
Builder concreteBuilder1 = new ConcreteBuilder1("大汉堡", "小薯条");
KFC kfc1 = new Director().build(concreteBuilder1);
System.out.println(kfc1);
//套餐B
System.out.println("======套餐B======");
Builder concreteBuilder2 = new ConcreteBuilder2("小汉堡", "小薯条");
KFC kfc2 = new Director().build(concreteBuilder2);
System.out.println(kfc2);
}
}

image-20230608210618559

到了这里你还是会觉得有点麻烦,你会发现,单品可有可无的选择上面你十分的被动,代码看上去也很怪,如果你下次想全部单品先选上,再去选套餐的时候,你又要新建一个新的指导者。

我觉得普通的建造者模式不适合参数的可有可无的选择,普通的建造者模式更侧重调控次序,在有些情况下需要简化系统结构

简化版的建造者模式

采用链式编程的方式

这种模式更加灵活,更加符合定义

既然Director是变化的,并且其实在生活中我们自己本身就是Director,所以这个时候我们可以把Director这个角色去掉,因为我们自身就是指导者

产品

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
/**
* 实体类`KFC
*/
public class KFC {
//套餐必点
private String hamburger;
private String chips;

//套餐选点
private String chicken;
private String cola;
private String pizza;
public KFC(String hamburger,String chips){
this.hamburger = hamburger;
this.hamburger = chips;
}
public void setChicken(String chicken) {
this.chicken = chicken;
}

public void setCola(String cola) {
this.cola = cola;
}

public void setPizza(String pizza) {
this.pizza = pizza;
}

@Override
public String toString() {
return "KFC{" +
"hamburger='" + hamburger + '\'' +
", chips='" + chips + '\'' +
", chicken='" + chicken + '\'' +
", cola='" + cola + '\'' +
", pizza='" + pizza + '\'' +
'}';
}
}

抽象建造者(Builder)

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 定义一个接口,表明需要建造什么,得到什么
*/
public abstract class Builder {
abstract Builder setChicken();

abstract Builder setCola();

abstract Builder setPizza();

abstract KFC getKFC();
}

具体建造者(ConcreteBuilder)

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
public class ConcreteBuilder extends Builder {
KFC kfc;
public ConcreteBuilder(String hamburger,String chips){
kfc = new KFC(hamburger,chips);
}
@Override
Builder setChicken() {
kfc.setChicken("鸡腿");
return this;
}

@Override
Builder setCola() {
kfc.setCola("可乐");
return this;
}

@Override
Builder setPizza() {
kfc.setPizza("披萨");
return this;
}

@Override
KFC getKFC() {
return kfc;
}
}

测试

1
2
3
4
5
6
public class BuilderTest {
public static void main(String[] args) {
KFC kfc = new ConcreteBuilder("汉堡", "薯条").setChicken().setCola().getKFC();
System.out.println(kfc);
}
}

image-20230608211039517

如果不需要抽象建造者的角色来规定生产内容,那么代码到这里其实还有进一步的简化空间。

使用静态内部类的方式

进一步简化

产品

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
package com.xiaofei.builder;

/**
* 实体类`KFC
*/
public class KFC {
//套餐必点
private String hamburger;
private String chips;

//套餐选点
private String chicken;
private String cola;
private String pizza;

//一定要有一个带有Builder参数的建造者
private KFC(Builder builder) {
this.hamburger = builder.hamburger;
this.chips = builder.chips;
this.chicken = builder.chicken;
this.cola = builder.cola;
this.pizza = builder.pizza;
}

//注意必须为静态内部类
public static class Builder {
//套餐必点
private String hamburger;
private String chips;

//套餐选点
private String chicken;
private String cola;
private String pizza;

public Builder(String hamburger, String chips) {
this.hamburger = hamburger;
this.chips = chips;
}

public Builder setChicken() {
this.chicken = "小鸡腿";
return this;
}

public Builder setCola() {
this.cola = "小可乐";
return this;
}

public Builder setPizza() {
this.pizza = "小披萨";
return this;
}

//生成一个产品
public KFC getKFC() {
return new KFC(this);
}
}

@Override
public String toString() {
return "KFC{" +
"hamburger='" + hamburger + '\'' +
", chips='" + chips + '\'' +
", chicken='" + chicken + '\'' +
", cola='" + cola + '\'' +
", pizza='" + pizza + '\'' +
'}';
}
}

测试

1
2
3
4
5
6
public class BuilderTest {
public static void main(String[] args) {
KFC kfc = new KFC.Builder("大汉堡", "小薯条").setChicken().setCola().getKFC();
System.out.println(kfc);
}
}

image-20230608211250487

建造者模式和抽象工厂模式的区别

通过上面的代码,你发现普通的建造者模式和抽象工厂模式真的很像,在建造者模式中的builder角色很像超级工厂,然后contracterBuilder很像具体的工厂,都是规定了建造的内容

那么它们之间 有什么区别呢

  • 建造者模式有指导者这个角色,直接返回一个组装好的产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族
  • 建造者模式更适合复杂的产品构建
  • 可以将抽象工厂模式理解成汽车零件生产工厂,而建造者模式看出组装工厂

总结

学习一类技巧是为了解决一类问题,学习设计模式主要是为了理解它的思想,将来遇到代码的编写,使用这种模式会更加的方式,而没有必要刻意的去使用,但是需要刻意的去练习,形成这种思想

优点:

  • 复杂产品的创建步骤分解在不同的方法中,这些方法可以调用顺序不同,结果不同,创建结果很清晰

缺点:

  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者来实现这种变化。