BrightLoong's Blog

设计模式——建造者模式

builder

一. 概述

建造者模式(Builder),又叫生成器模式,它将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,使用建造者模式,用户就只需指定需要建造的类型就可以得到它们,二具体建造的过程和细节就不需要知道了。

建造者模式相比于工厂模式更加关注组成部分的装配细节和顺序。

创建者模式属于创建型模式。

二. UML类图解析

建造者模式的UML类图如下:

builder

  • Builder:为创建一个Product对象的各个部件指定的抽象接口,buildPart()是对象组成部分构造的抽象方法,根据需求可以有多个组成部分的构造方法。
  • ConcreteBuilder:具体建造者,继承自Builder,构造和装配各个部件,getResult()返回构造后的实例。
  • Director:指挥者,持有一个Builder引用,调用具体的创建者的各个部件来创建对象,负责保证对象各部分完整创建或者按某种顺序创建 。
  • Product:产品,要创建的具体对象,一般来说是一个复杂的对象,包含多个组成部分。

三. 代码实现

这里把电脑作为一个产品,一般来说一个可以使用的台式电脑包括显示器、主机、键盘和鼠标,各个组成部分可以有不同的品牌、性能、型号等细节,下面就使用建造者模式来对电脑进行实现。

产品类——Computer

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
package io.github.brightloong.design.builder;

/**
* 产品类Computer,为了方便实现,所有组成均用String来表示。
* Created by BrightLoong on 2018/5/24.
*/
public class Computer {
/**
* 显示器
*/
public String displayer;

/**
* 主机
*/
public String host;

/**
* 键盘
*/
public String keyboard;

/**
* 鼠标
*/
public String mouse;

public String getDisplayer() {
return displayer;
}

public void setDisplayer(String displayer) {
this.displayer = displayer;
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public String getKeyboard() {
return keyboard;
}

public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}

public String getMouse() {
return mouse;
}

public void setMouse(String mouse) {
this.mouse = mouse;
}

/**
* 覆盖toString方法.
* @return
*/
@Override
public String toString() {
return "Computer{" +
"displayer='" + displayer + '\'' +
", host='" + host + '\'' +
", keyboard='" + keyboard + '\'' +
", mouse='" + mouse + '\'' +
'}';
}
}

构建抽象——ComputerBuilder

1
2
3
4
5
6
7
8
9
10
11
12
package io.github.brightloong.design.builder;

/**
* Created by BrightLoong on 2018/5/24.
*/
public abstract class ComputerBuilder {
abstract void buildDisplay();
abstract void buildHost();
abstract void buildKeyBoard();
abstract void buildMouse();
abstract Computer getComputer();
}

具体构建类——SuperComputerBuilder

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
package io.github.brightloong.design.builder;

/**
* Created by BrightLoong on 2018/5/24.
*/
public class SuperComputerBuilder extends ComputerBuilder {
Computer computer;

public SuperComputerBuilder() {
computer = new Computer();
}

void buildDisplay() {
computer.setDisplayer("37.5英寸显示器IPS曲面屏微边框防眩光4K屏幕");
}

void buildHost() {
computer.setHost("i7-6950X/TiTANX 高端硬管水冷电脑六核游戏主机");
}

void buildKeyBoard() {
computer.setKeyboard("猛禽竞技机械键盘");
}

void buildMouse() {
computer.setMouse("逆天悬浮鼠标");
}

Computer getComputer() {
return computer;
}
}

指挥者——ComputerDirector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package io.github.brightloong.design.builder;

/**
* Created by BrightLoong on 2018/5/24.
*/
public class ComputerDirector {
private ComputerBuilder computerBuilder;

public ComputerDirector(ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}

public Computer build() {
computerBuilder.buildDisplay();
computerBuilder.buildHost();
computerBuilder.buildKeyBoard();
computerBuilder.buildMouse();
return computerBuilder.getComputer();
}
}

客户端调用和输出

1
2
3
4
5
6
7
8
9
10
11
package io.github.brightloong.design.builder;

/**
* Created by BrightLoong on 2018/5/24.
*/
public class Client {
public static void main(String[] args) {
Computer computer = new ComputerDirector(new SuperComputerBuilder()).build();
System.out.println(computer);
}
}

输出:

1
Computer{displayer='37.5英寸显示器IPS曲面屏微边框防眩光4K屏幕', host='i7-6950X/TiTANX 高端硬管水冷电脑六核游戏主机', keyboard='猛禽竞技机械键盘', mouse='逆天悬浮鼠标'}

四. 总结

使用场景

  • 用于创建一些复杂的对象,这些对象内部构建的顺序通常是稳定的,但对象内部的构建通常面临着复杂的变化。
  • 当创建复杂的对象的算法应该独立于该对象的组成部分已经它们的装配方法时。

优点

  • 建造者模式使得建造代码与表示代码分离,隐藏了产品实现细节。
  • 如果要改变一个产品的内部表示,只要再定义一个具体的建造者就可以了,方便扩展。
  • 建造者模式通过指挥者可以控制组件建造顺序,能实现一定程度的细节把控,特别是那种生成的产品对象之间存在属性依赖的情况。

缺点

  • 如果产品多变,会生成大量的建造类,造成类膨胀。

五. 扩展——构建器

在《Effective Java》一书中,第2条提到:

遇到多个构造器参数时要考虑用构建器。

当有多个参数的时候,客户端代码不仅难编写,也很难阅读,再有,客户端可能不清楚每个参数到底代表什么。

这里也使用到了Builder模式,不直接生成想要的对象,二是让客户端利用所有必要的参数调用Builder构造器,得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变(相比较JavaBeans模式,也就是调用set方法来进行设值,这种方式可以生成不可变对象)的对象。下面的是具体的代码,来自《Effective Java》书上的列子。

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
package io.github.brightloong.design.builder;

/**
* Created by BrightLoong on 2018/5/24.
*/
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public static class Builder {
//Required parameters
private final int servingSize;
private final int servings;

//Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

public Builder calories(int val) {
calories = val;
return this;
}

public Builder fat(int val) {
fat = val;
return this;
}

public Builder sodium(int val) {
sodium = val;
return this;
}

public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}

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

}

private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}

@Override
public String toString() {
return "NutritionFacts{" +
"servingSize=" + servingSize +
", servings=" + servings +
", calories=" + calories +
", fat=" + fat +
", sodium=" + sodium +
", carbohydrate=" + carbohydrate +
'}';
}
}

调用和输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package io.github.brightloong.design.builder;

/**
* Created by BrightLoong on 2018/5/24.
*/
public class Client2 {
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
.calories(100).sodium(35)
.carbohydrate(27)
.build();
System.out.println(cocaCola);
}
}

输出:

1
NutritionFacts{servingSize=240, servings=8, calories=100, fat=0, sodium=35, carbohydrate=27}
坚持原创技术分享,您的支持将鼓励我继续创作!