原型模式和建造者模式

原型模式

原型模式就是从一个原型克隆出多个一模一样的对象的模式。原型模式对于这样的情形适用——需要一个和已有的对象大体相同的对象,但无法接受从零开始创建该对象。举例来说,当编写 PPT 时,当上一页和当前页面元素基本一致的时候,就将上一页复制,进行一些编辑即可,从而免去再次的排版等工序。

原型模式的角色有抽象原型,具体原型和客户端。其中抽象原型类是一个提供 clone 方法的接口(Java 本身有一个这样作用的接口 Cloneable,但它仅仅起标记作用)。具体原型类是实现了该接口的类。客户端使用时调用原型类的 clone 方法。

对于 Java,clone 方法的实现,应当通过构造函数定义一个 Object 对象(在这里,Object 负责抽象原型类的角色,它就是 Prototype),其值为 super.clone(),并设定其各个域成员与被克隆的对象一致(需注意浅拷贝和深拷贝,深拷贝可以简单使用序列化接口来实现),最后强制类型转换并返回该对象。其代码形式如下——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 这个类应当实现 Cloneable 接口
public Prototype clone() {
Object obj = null;
try {
obj = super.clone(); // Object 所定义的方法,其是 native 的。
// 设定各种属性与该对象一致
// . . .
return (Prototype) obj;
}
catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}

还可使用所谓的原型管理器——其实质是一个、<String, Prototype>的 HashMap,其通过单例模式创建,维护着所需的原型对象,并应客户端的要求返回特定的对象。

原型模式的优点在于能够节约初始化的成本(特别是需要调用硬盘甚至网络资源的时候),简化复杂实例的创建,且其扩展性高,可以针对抽象原型类进行编程,而具体原型类编写在配置文件中,增加,减少具体原型类都不需要修改源代码(甚至修改也可能可以不需要,不过可能会造成配置文件繁琐?)。

缺点在于深拷贝时代码可能比较复杂,且克隆方法必须位于类的内部,修改时需要修改源代码,违反开闭原则。

Spring 的 IoC 容器对 Bean 的创建可以选择使用单例模式或原型模式。

建造者模式

建造者模式就像游戏中的捏人系统,人的外观分为几个特定的部分,如发型,脸型,五官,躯干……一次捏人的完成,是对各个部分都进行“建造”,最后将其组合出结果(当然,这里没有组合,或许也可以联想飞船的各模块的建造……就像 Space Engineer 里那样的?)。

建造者模式的实质就是将复杂对象(具有多个实例域的对象)的构建和对象的表示(其属性)相分离,建造者将对象所属的类中各个共通的部分抽象成一个个模块,按模块地进行建设(比如划分为登陆模块,控制模块,推进模块,战斗模块……),最后将各个模块进行组合,得出最终的舰船。而非是直接上手创建这样一个复杂的对象——抽象啊抽象!这就和用汇编写 http 服务器一样离谱。

建造者模式有如下角色:抽象建造者——提供各部分的构造方法的接口,以及返回最终对象的方法的接口。这里的抽象建造者可以是抽象类(它可以维护一个最终产品并直接实现返回最终对象的方法),也可以是接口;产品——建造的对象;建造者——对抽象建造者的实现;导演——指挥建造者进行建造并返回结果(也就是调用建造者提供的方法返回最终对象)(这个导演倒是可以直接设定成抽象建造者的一个方法…这便使用了类似模板方法的一种设计…这样简化了系统的结构,但可能会让抽象建造者类职责过重);客户端——与导演交互,获取最终对象。

类图如下——

然后是各个角色的一般形式——

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
// 复杂对象
class Product {
private dataA partA;
private dataB partB;
private dataC partC;
// 和其 getter 和 setter
}

// 抽象建造者类
abstract class Builder {
protected Product product = new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product getResult() {
return product;
}
}

// 导演类
class Director { // 导演类大可直接作为 Builder 类的一个方法
private Builder builder;

public Director(Builder builder) {
this.builder = builder;
}

public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}

// 具体建造者
class ConcreteBuilder extends Builder {
public void buildPartA() {
// blablabla
}

public void buildPartB() {
// blablabla
}

public void buildPartC() {
// blablabla
}
}

客户端形式如下——

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder(); // 显然,这里的 new 可以使用工厂模式干掉以(无论是简单工厂还是工厂方法模式)
Director director = new Director(builder);
Product product = director.construct();
// ... 如此复杂,果然 construct 方法应该直接放到建造者类中,
//无论是抽象建造者类中还是具体建造者类中(取决于有无必要对 construct 方法进行重载)
//然后使用工厂方法进行进一步抽象,让其能够这样调用——
// Product product = BuilderFactory.createBuilder("angel").construct(); // 这里的 createBuilder 方法既可以返回具体建造者,也可以返回一个导演类……
// . . .
}
}

可见还是简化后的形式更加舒服。

关于建造过程,其实能够给其添加更多程序结构来更加精细地控制建造过程。比如添加所谓的钩子方法(Hook Method),它控制是否需要调用某个 buildPartX() 方法。

钩子方法的形式是置于抽象建造者类中的一个返回布尔值的方法,来决定某个部分的是否构建。比如对于一个建造船舰的抽象建造者类 ShipBuilder,给定一个名为 isArmed 的方法,对于运输船,它不需要火力模块,因此对于这个方法它就返回 false。导演类就可以这样 construct——

1
2
3
4
5
6
7
public Product construct() {
shipBuilder.buildPowerModule(); // 无论什么船,都需要能源,对吧?
if (shipBuilder.isArmed())
shipBuilder.buildFireModule();
// . . .
return builder.getResult();
}

一个练习——某视频播放软件有多种界面显示模式——标准模式,精简模式,编辑模式等等。不同界面显示模式中组成元素有差异。其中,各界面现实如下元素——

  • 标准模式:播放窗口,控制条,显示菜单,播放列表
  • 编辑模式:播放窗口,控制条,显示菜单,编辑栏
  • 精简模式:播放窗口,控制条

可以使用一些钩子函数对窗口的构建进行精细控制。

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/**
* - 完整模式:播放窗口,控制条,显示菜单,播放列表
- 编辑模式:播放窗口,控制条,显示菜单,编辑条
- 精简模式:播放窗口,控制条
*/

// 产品,这里为简洁需要。.
class Window {
// . . .
}
// 抽象建造类,导演类包含在其中
abstract class UIBuilder {
private Window window = new Window();
public abstract void buildPlayWindow();
public abstract void buildCtrlBar();
public abstract void buildDisplayMenu();
public abstract void buildPlayList();
public abstract void buildEditBar();
public boolean needDisplayMenu() {
return true;
}
public boolean needPlayList() {
return true;
}
public boolean needEditBar() {
return false;
}
public Window createWindow() {
return window;
}
public static final Window construct(UIBuilder builder) {
builder.buildPlayWindow();
builder.buildCtrlBar();
if (builder.needDisplayMenu())
builder.buildDisplayMenu();
if (builder.needPlayList())
builder.buildPlayList();
if (builder.needEditBar())
builder.buildEditBar();
return builder.createWindow();
}
}
// 三个具体建造类
class StandardWindowBuilder extends UIBuilder {

@Override
public void buildPlayWindow() {
System.out.println("标准模式:播放窗口");
}

@Override
public void buildCtrlBar() {
System.out.println("标准模式:控制条");
}

@Override
public void buildDisplayMenu() {
System.out.println("标准模式:显示菜单");
}

@Override
public void buildPlayList() {
System.out.println("标准模式:播放列表");
}
@Override
public void buildEditBar() {
System.out.println("不应该被执行");
}
@Override
public boolean needDisplayMenu() {
return true;
}
@Override
public boolean needEditBar() {
return false;
}
@Override
public boolean needPlayList() {
return true;
}
}
class EditWindowBuilder extends UIBuilder {

@Override
public void buildPlayWindow() {
System.out.println("编辑模式:播放窗口");
}
@Override
public void buildCtrlBar() {
System.out.println("编辑模式:控制条");
}
@Override
public void buildDisplayMenu() {
System.out.println("编辑模式:显示菜单");
}

@Override
public void buildPlayList() {
System.out.println("不应该被执行");
}
@Override
public void buildEditBar() {
System.out.println("编辑模式:编辑条");
}
@Override
public boolean needDisplayMenu() {
return true;
}
@Override
public boolean needEditBar() {
return true;
}
@Override
public boolean needPlayList() {
return false;
}
}
class MinusWindowBuilder extends UIBuilder {

public void buildPlayWindow() {
System.out.println("精简模式:播放窗口");

}
public void buildCtrlBar() {
System.out.println("精简模式:控制条");
}
public void buildDisplayMenu() {
System.out.println("不应该被执行");
}

public void buildPlayList() {
System.out.println("不应该被执行");
}

public void buildEditBar() {
System.out.println("不应该被执行");
}
@Override
public boolean needDisplayMenu() {
return false;
}
@Override
public boolean needEditBar() {
return false;
}
@Override
public boolean needPlayList() {
return false;
}
}

// 客户端
public class Main {
public static void main(String[] args) {
// 可以使用反射或者工厂模式干掉这个 new
Window window = UIBuilder.construct(new EditWindowBuilder());
// . . .
}
}

天啊……这代码长的离谱。

建造者模式的优点在于,客户端不需要知道产品内部的细节(这同工厂模式是一样的),产品本身同产品的构建分离(客户端不需要自己 new 和其他初始化操作);每一个具体建造者是相对独立的,系统扩展方便,符合开闭原则;产品的创建过程能够分散在不同的方法中,从而更加清晰,易维护。

缺点在于,其只能创建有较多共同点的产品(如人的外观,船舰等),如果差异性很大(比如人类和外星人的船舰,人和动物),很多部分都不相同(从而不能抽象出一般的创建过程),就无法使用建造者模式。

另一种实现

下面在公众号上看到的对建造者模式的另一种实现。客户端使用new Ship.Builder("powerModule").setFireModule("fireModule").setTransportModule("transportModule").build();这样的形式进行调用。这也可以通过另一个类(那么其实它才是真正的“建造者”)来实现。

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
class Ship { 
// 当然,这些模块不可能是字符串…火力模块也不可能单独来一个
private String powerModule;
private String fireModule;
private String transportModule;
private Ship(Builder builder) { // 包访问限定
this.powerModule = builder.powerModule;
this.fireModule = builder.fireModule;
this.transportModule = builder.transportModule;
}

// 这个建造者大可以放到外面
public static class Builder {
// 每一个属性都要定义一个相同的……老实说有点蛋疼
private String powerModule;
private String fireModule;
private String transportModule;
public Builder(String powerModule) {
this.powerModule = powerModule;
}
public Builder setFireModule(String fireModule) {
this.fireModule = fireModule;
return this;
}
public Builder setTransportModule(String transportModule) {
this.transportModule = transportModule;
return this;
}
public Ship build() {
return new Ship(this);
}
}
}

public class Main {
public static void main(String[] args) {
// 众所周知,客户端不应该自己 new 东西~
Ship ship = new Ship.Builder("powerModule")
.setFireModule("fireModule")
.setTransportModule("transportModule")
.build();
// . . .
}
}


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!