02 设计模式——建造者模式

返回设计模式博客目录

介绍


建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

Builder 模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。该模式是为了将构建复杂对象的过程和它的部件解耦,使得构建过程和部件的表示隔离开来。

因为一个负责对象有大量组成部分,如汽车有车轮、方向盘、发动机、车盘,还有各种小零件,如何将这些部件装配成一辆汽车,这个装配过程很漫长,也很复杂,对于这种情况,为了在构建过程中对外部隐藏实现细节,就可以使用 Builder 模式将部件和组装过程分离,使得构建过程和部件都可以自由扩展,两者之间的耦合也降到最低。

使用场景

  • 相同的方法,不同的执行顺序,产生不同的事件结果时。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时。
  • 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候使用建造者模式非常合适。
  • 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值时。

UML 类图

从上图可以看到,经典 Buider 模式中有四个角色:

  • Product 产品类 —— 产品的抽象类;
  • Builder —— 抽象 Builder 类,规范产品的组建,一般是由子类实现具体的组建过程;
  • ConcreteBuilder —— 具体的 Builder 类;
  • Director —— 统一组装过程。

示例


计算机的组装过程较为复杂,并且组装顺序是不固定的,为了易于理解,我们把计算机组装的过程简化为构建主机、设置操作系统、设置显示器 3 个部分,然后通过 Director 和具体的 Builder 来构建计算机对象。请看下面示例。

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
// 计算机抽象类,即 Product 角色
public abstract class Computer {
protected String mBoard;
protected String mDisplay;
protected String mOS;
protected Computer() {}
public void setBoard(String board) {
this.mBoard = board;
}
public void setDisplay(String display) {
this.mDisplay = display;
}
public abstract void setOS();
@Override
public String toString() {
return "Computer [" + "mBoard='" + mBoard + '\'' +
", mDisplay='" + mDisplay + '\'' + ", mOS='" + mOS + ']';
}
}
// 具体的 Computer 类,Macbook
public class Macbook extends Computer {
protected Macbook() {}
@Override
public void setOS() {
mOS = "Mac OS X 10.10";
}
}
// 抽象 Builder 类
public abstract class Builder {
public abstract void buildBoard(String board);
public abstract void buildDisplay(String display);
public abstract void buildOS();
public abstract Computer create();
}
// 具体的 Builder 类,MacbookBuilder
public class MacbookBuilder extends Builder {
private Computer mComputer = new Macbook();
@Override
public void buildBoard(String board) {
mComputer.setBoard(board);
}
@Override
public void buildDisplay(String display) {
mComputer.setDisplay(display);
}
@Override
public void buildOS() {
mComputer.setOS();
}
@Override
public Computer create() {
return mComputer;
}
}
// Director 类,负责构造 Computer
public class Director {
Builder mBuilder = null;
public Director(Builder builder) {
this.mBuilder = builder;
}
public void construct(String board, String display) {
mBuilder.buildBoard(board);
mBuilder.buildDisplay(display);
mBuilder.buildOS();
}
}
// 测试代码
public class Test {
public static void main(String[] args) {
Builder builder = new MacbookBuilder();
Director pcDirector = new Director(builder);
pcDirector.construct("英特尔主板", "Retina 显示器");
System.out.println("Computer Info : " + builder.create().toString());
}
}

输出结果:

Computer Info : Computer [mBoard=英特尔主板, mDisplay=Retina 显示器, mOS=Mac OS X 10.10]

上述示例中,通过具体的 MacbookBuilder 来构建 Macbook 对象,而 Director 封装了构建复杂产品对象的过程,对外隐藏构建细节。Builder 与 Director 一起将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的对象。

值得注意的是,在现实开发过程当中,Director 角色经常会被省略。而直接使用一个 Builder 来进行对象的组装,这个 Builder 通常为链式调用,它的关键点是每个 setter 方法都返回自身,也就是 return this,这样就使得 setter 方法可以链式调用,代码大致如下。

1
new TestBuilder().setA("A").setB("B").create();

通过这种形式不仅去除了 Director 角色,整个结构也更加简单,也能对 Product 对象的组装过程有更精细的控制。

ANDROID 源码中的 Builder 模式


在 ANDROID 源码中,最常用到的 Builder 模式就是 AlertDialog.Builder,使用该 Builder 来构建复杂的 AlertDialog 对象。在开发过程中,我们经常用到 AlertDialog,具体示例如下。

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
private void showDialog(final Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setIcon(R.drawable.taiji)
.setTitle("Title")
.setMessage("Message")
.setPositiveButton("Button1", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(context, "Button1", Toast.LENGTH_SHORT).show();
}
})
.setNeutralButton("Button2", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(context, "Button2", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("Button3", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(context, "Button3", Toast.LENGTH_SHORT).show();
}
})
.create()
.show();
}

显示结果如下图所示。

从类名就可以看出这就是一个 Builder 模式,通过 Builder 对象来组装 Dialog 的各个部分,如 title、buttons、message 等,将 Dialog 的构造和表示进行分离。下面看看 AlertDialog 的相关源码。

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
public class AlertDialog extends AppCompatDialog implements DialogInterface {
// AlertController 接收 Builder 成员变量 p 中的各个参数
final AlertController mAlert;
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
// 构造 AlertController
mAlert = new AlertController(getContext(), this, getWindow());
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
// 代码省略
public static class Builder {
// 1、存储 AlertDialog 的各个参数,如 title、message、icon 等
private final AlertController.AlertParams P;
private final int mTheme;
public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
// 代码省略
// 2、设置各种参数
public Builder setTitle(@Nullable CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
public Builder setView(View view) {
P.mView = view;
P.mViewLayoutResId = 0;
P.mViewSpacingSpecified = false;
return this;
}
// 3、构建 AlertDialog,传递参数
public AlertDialog create() {
// 4、调用 new AlertDialog 构造对象,并且将参数传递给个体 AlertDialog
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
// 5、将 P 中的参数应用到 dialog 中的 mAlert 对象中
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
}
}

上述代码中,Builder 类可以设置 AlertDialog 中的 title、message、button 等参数,这些参数都存储在类型为 AlertController.AlertParams 的成员变量 P 中,AlertController.AlertParams 中包含了与 AlertDialog 视图中对应的成员变量。在调用 Builder 类的 create 函数时会创建 AlertDialog,并且将 Builder 成员变量 P 中保存的参数应用到 AlertDialog 的 mAlert 对象中,即 P.apply(dialog.mAlert) 代码段。我们再看看 apply 函数的实现。

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
public void apply(AlertController dialog) {
if (mCustomTitleView != null) {
dialog.setCustomTitle(mCustomTitleView);
} else {
if (mTitle != null) {
dialog.setTitle(mTitle);
}
if (mIcon != null) {
dialog.setIcon(mIcon);
}
if (mIconId != 0) {
dialog.setIcon(mIconId);
}
if (mIconAttrId != 0) {
dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId));
}
}
if (mMessage != null) {
dialog.setMessage(mMessage);
}
if (mPositiveButtonText != null || mPositiveButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null, mPositiveButtonIcon);
}
if (mNegativeButtonText != null || mNegativeButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null, mNegativeButtonIcon);
}
if (mNeutralButtonText != null || mNeutralButtonIcon != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null, mNeutralButtonIcon);
}
// For a list, the client can either supply an array of items or an
// adapter or a cursor
if ((mItems != null) || (mCursor != null) || (mAdapter != null)) {
createListView(dialog);
}
if (mView != null) {
if (mViewSpacingSpecified) {
dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop,
mViewSpacingRight, mViewSpacingBottom);
} else {
dialog.setView(mView);
}
} else if (mViewLayoutResId != 0) {
dialog.setView(mViewLayoutResId);
}
}

在 apply 函数中,只是将 AlertParams 参数设置到 AlertController 中,例如,将标题设置到 Dialog 对应的标题视图中,将 Message 设置到内容视图中等。当我们获取到 AlertDialog 对象后,通过 show 函数就可以显示这个对话框。而 show 函数最终调用的是 Dialog 类的 show 函数。

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
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener,
Window.OnWindowDismissedCallback {
// 代码省略
public void show() {
// 已经是显示状态,则return
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
// 1、onCreate 调用
if (!mCreated) {
dispatchOnCreate(null);
}
// 2、onStart
onStart();
// 3、获取 DecorView
mDecor = mWindow.getDecorView();
// 代码省略
// 4、获取布局参数
WindowManager.LayoutParams l = mWindow.getAttributes();
// 5、将 mDecor 添加到 WindowManager 中
mWindowManager.addView(mDecor, l);
mShowing = true;
// 发送一个显示 Dialog 的消息
sendShowMessage();
}
}

在 show 函数中主要做了如下几个事情:

  • 通过 dispatchOnCreate 函数来调用 AlertDialog 的 onCreate 函数;
  • 然后调用 AlertDialog 的 onStart 函数;
  • 最后将 Dialog 的 DecorView 添加到 WindowManager 中。

这里的 AlertDialog.Builder 同时扮演了上文中提到的 Builder、ConcreteBuilder、Director 的角色,简化了 Builder 模式的设计。当模块比较稳定,不存在一些变化时,可以在经典模式实现的基础上做出一些精简,而不是照搬 GOF 上的经典实现,更不要生搬硬套,使程序失去架构之美。

Builder 模式实战


配置 ImageLoader 的参数:缓存、图片加载中显示的图片、加载失败后显示的图片、图片加载策略、线程数等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ImageLoader {
// 图片加载配置对象
private ImageLoaderConfig mConfig;
// 省略单例模式代码
// 初始化 ImageLoader
public void init(ImageLoaderConfig config) {
mConfig = config;
// 检测配置的合法性,内部会根据配置做一些初始化操作
checkConfig();
// 代码省略
}
// 代码省略
}

把配置的代码基本上都封装到了 ImageLoaderConfig 和 Builder 对象中。其代码如下:

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
public class ImageLoaderConfig {
// 图片缓存配置
private BitmapCache bitmapCache = new MemoryCache();
// 加载图片时的 loading 和加载失败的图片配置对象
private DisplayConfig displayConfig = new DisplayConfig();
// 加载策略
private LoadPolicy loadPolicy = new SerialPolicy();
// 线程数量,默认为 CPU 数量 + 1
private int threadCount = Runtime.getRuntime().availableProcessors() + 1;
private ImageLoaderConfig() {}
/**
* 配置类的 Builder
*/
public static class Builder {
BitmapCache bitmapCache = new MemoryCache();
DisplayConfig displayConfig = new DisplayConfig();
LoadPolicy loadPolicy = new SerialPolicy();
int threadCount = Runtime.getRuntime().availableProcessors() + 1;
public Builder setThreadCount(int threadCount) {
this.threadCount = threadCount;
return this;
}
public Builder setCache(BitmapCache bitmapCache) {
this.bitmapCache = bitmapCache;
return this;
}
public Builder setLoadingPlaceholder(int resId) {
displayConfig.loadingResId = resId;
return this;
}
public Builder setNotFoundPlaceholder(int resId) {
displayConfig.failedResId = resId;
return this;
}
public Builder setLoadPolicy(LoadPolicy loadPolicy) {
if (loadPolicy != null) {
loadPolicy = loadPolicy;
}
return this;
}
void applyConfig(ImageLoaderConfig config) {
config.bitmapCache = this.bitmapCache;
config.displayConfig = this.displayConfig;
config.loadPolicy = this.loadPolicy;
config.threadCount = this.threadCount;
}
// 根据已经设置好的属性创建配置对象
public ImageLoaderConfig create() {
ImageLoaderConfig config = new ImageLoaderConfig();
applyConfig(config);
return config;
}
}
}

通过将 ImageLoaderConfig 的构造函数、字段私有化,使得外部不能访问内部属性,用户唯一能够设置属性的地方就是通过 Builder 对象了,也就是说用户只能通过 Builder 对象构造 ImageLoaderConfig 对象,这就是构建和表示相分离。

用户的使用代码如下所示。

1
2
3
4
5
6
7
8
9
private void initImageLoader() {
ImageLoaderConfig config = new ImageLoaderConfig.Builder()
.setLoadingPlaceholder(R.drawable.loading)
.setNotFoundPlaceholder(R.drawable.not_found)
.setCache(new DoubleCache(this))
.setThreadCount(4)
.setLoadPolicy(new ReversePolicy()).create();
ImageLoader.getInstance().init(config);
}