设计模式:建造者模式
场景再现
在之前的文章中我们提到过 工厂模式,这个模式把对象创建的任务交由子类,降低了代码重复和耦合。工厂(方法)模式的核心是 factoryMethod
,这个方法里封装了对象的定义以及生成,客户端完全不需要知道这里面的复杂过程,只需要向 factoryMethod
中传入参数即可。但是我们知道,对象的创建有时没有这么简单,很多时候客户端需要定制化对象,而仅仅通过一个 factoryMethod
是很难做到这一点的,即使能做到,factoryMethod
的实现逻辑也会变的相当复杂。
我们来看一个用户点餐的例子,假设一家餐馆卖炸鸡,用户可以通过系统定制化菜品,比如分量、辣度、麻度、是否加入其他的酱以及一些特殊的要求,具体如下图所示:
可以看到,针对这一个菜品,用户可以非常灵活地对其进行定制化操作,如果是使用前面提到的工厂模式,我们需要在 factoryMethod
里面实现非常多的细节,客户端还需要将所有的定制化参数传入到 factoryMethod
中,这都会导致模块的整体复杂度上升,并且工厂类与客户端通过复杂的参数耦合在一起,这会造成更大的维护成本。那更好的方式是什么呢?
既然客户端需要更加灵活地来配置对象,那么或许我们没必要将对象定制化这一过程放到工厂类中,工厂类只提供一系列可供客户端操作的方法,由客户端来自行决定如何使用这些方法。比如这里,我们可以先定义一个接口:
1
2
3
4
5
6
7
8
interface AbstractBuilder {
void buildSize(String s);
void buildSpicy(String s);
void buildPepper(String p);
void addSource(String source);
void addComment(String comment);
Dish getCustomizedDish();
}
然后实现我们的炸鸡类,这里我只列出大致的代码框架,省略具体的实现细节:
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
class FriedChicken extends Dish { /*...*/ }
class FriedChickenBuilder implements AbstractBuilder {
private Dish friedChicken;
FriedChickenBuilder() {
friedChicken = new FriedChicken();
}
void buildSize(String s) {
// ...
}
void buildSpicy(String s) {
// ...
}
void buildPepper(String p) {
// ...
}
void addSource(String source) {
// ...
}
void addComment(String comment) {
// ...
}
Dish getCustomizedDish() {
return this.friedChicken;
}
}
有了上面这些类,客户端就可以灵活地自定义新对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Client {
void constructDish() {
AbstractBuilder builder = new FriedChickenBuilder();
// build dish
builder.buildSize("M");
builder.buildSpicy("H");
// ...
Dish customizedFriedChicken = builder.getCustomizedDish();
}
}
模式解析
从上面的例子我们可以看到,建造者模式的类结构关系直接明了,可以用下图来表示:
之所以简单,是因为我们把对象定义的环节放到了客户端,而创建类仅仅是为客户端提供了相对应的方法,那这是不是意味着客户端和创建类的耦合增加了呢?其实不然,因为客户端和创建类都是依赖于抽象接口(AbstractBuilder
),客户端只是知道了接口所定义的方法,对每个方法背后的具体实现一无所知,另外,客户端仅仅是调用接口中的方法,并不直接负责对象的创建,对象的创建还是封装在创建类中,并交由创建类来全权负责。
那这个模式具体有什么样的好处呢?我们该在什么样的场景下使用它呢?首先,建造者模式可以让我们构建较为复杂的对象,也就是当对象的创建中涉及到许许多多的条件参数,对象的创建会根据参数的不同而不同,这个时候可以考虑建造者模式。另外,建造者模式让客户端可以分步骤来创建对象,这也是它和工厂模式在表现形式上的不同。还有就是建造者模式下,目标对象类(例子中的 FriedChicken
)是被隐藏在创建类(例子中的 FriedChickenBuilder
)中的,客户端并不知道这些细节,客户端知道的仅仅是抽象接口中定义的方法,因而这给创建类的实现上增加了灵活性,比如一个创建类可以根据不同条件创建不同种类的目标对象。
既然有好处,也就会有不足。相比于工厂方法模式下直接调用一个函数就可以创建对象,建造者模式很多时候需要客户端需要有一定的 domain knowledge,其实说白了就是需要清楚那些抽象接口中定义的方法,以及每个创建参数所代表的意义,想要参与到对象的定义以及灵活构建一个复杂对象,这些东西往往是绕不开的。当然了,如果创建的是一个简单的对象,工厂方法模式会更简洁,也更友好些。