设计模式之builder模式

参考:

前言

builder模式也叫建造者模式, 也是Android中应用十分广泛的设计模式之一.
举个例子, 比如我们经常使用的框架okhttp和retrofit

1
2
3
4
5
6
OkHttpClient client = new Builder().writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.cache(cache)
.build();
1
2
3
4
5
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okhttpClient)
.build();

还有Android中AlerDialog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new AlertDialog.Builder(this).setTitle("这是标题")
.setMessage("这是Message")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {

}
})
.create()
.show();

所以我们赶快去探究一下这神器的builder模式吧

Builder模式介绍

该模式是为了将构建复杂对象的过程和它的部件解耦, 构建过程和部件都可以自由扩展, 降低耦合.举例来说, 如果一个类有很多种构造方法, 或者一个构造方法中需要传入很多的参数, 比如说需要十几个参数, 可想而知这样的构造函数是十分容易出错的.而如果使用set方法来依次传入参数, 又失去了链式调用的优雅性, 代码变得不连续.这个时候使用builder模式就非常适合了.

Builder模式的UML类图


这时候又要再次科普一下UML类图中各种图形的含义了(因为我每次都容易忘记).
每个矩形都代表一个类(包括class和interface), 比如中间的那个Builder, 第一层显示它的类名是Builder, 顶端的《abstract》表示这是一个抽象类, 第二层表示它有三个方法, ‘+’表示方法是public的, 三个方法的返回类型都是void.
空心三角形 + 实线 表示ConcreteBuilder是继承自Builder的
箭头 + 虚线 表示依赖关系, 箭头指向被使用者, 比如这里的意思就是说ConcreteBuilder依赖Product, 因为ConcreteBuilder在组装的时候还是需要一个Product的, 不然它把零件组装到哪里去呢?也就是说ConcreteBuilder持有Product的引用.
空心菱形 + 实线(或者箭头线) 表示聚合关系, 汽车对象由轮胎对象聚合而成,但是轮胎对象的生命期并不受汽车对象的左右。当汽车对象销毁时,轮胎对象也可以单独存在.在这里, Director中必定有一个变量能够指向Builder, 表示聚合关系.

经典的Builder模式

Product

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
/**
* 计算机抽象类, 即Product角色
*/
public abstract class Computer {

protected String mBoard;
protected String mDisplay;
protected String mOS;

public Computer() {
}

public void setBoard(String board) {
mBoard = board;
}

public void setDisplay(String display) {
mDisplay = display;
}

public abstract void setOS();

@Override
public String toString() {
return "Computer{"
+ "mBoard='"
+ mBoard
+ '\''
+ ", mDisplay='"
+ mDisplay
+ '\''
+ ", mOS='"
+ mOS
+ '\''
+ '}';
}
}

具体的Product

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 具体的Computer类
*/
public class Macbook extends Computer {

public Macbook() {
}

@Override
public void setOS() {
mOS = "Mac OS X 10.10";
}
}

抽象Builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 抽象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();

}

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
/**
* 具体的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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Director类, 负责构造Computer
*/
public class Director {

Builder mBuilder;

public Director(Builder builder) {
mBuilder = builder;
}

public void construct(String board, String display) {
mBuilder.buildBoard(board);
mBuilder.buildDisplay(display);
mBuilder.buildOS();
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
public class Main {

public static void main(String[] args) {
Builder builder = new MacbookBuilder();
Director pcDirector = new Director(builder);
pcDirector.construct("英特尔主板", "Retina 显示器");
Computer macBook = builder.create();
System.out.println("Computer Info: " + macBook.toString());
}
}

输出结果:

1
Computer Info: Computer{mBoard='英特尔主板', mDisplay='Retina 显示器', mOS='Mac OS X 10.10'}

可以看出, 经典的 Builder 模式重点在于抽象出对象创建的步骤,并通过调用不同的具体实现类从而得到不同的结果, 但是在创建过程中依然要传入多个参数, 不是很方便, 所以有了变种的Builder模式

变种的Builder模式

目前来说在 Android&Java 开发过程中经典的 Builder 模式使用的较少,一般广泛使用的是他的一个变种.
在日常的开发中 Director 角色经常会被忽略,这样会相对的减少了构造的步骤而直接使用一个 Builder 来进行对象的组装.
这里我要说的就是一种内部Builder并且能够链式调用的变种.
我们就直接进入实战, 模仿AlertDialog写一个dialogfragment, 但是比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
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
/**
* Created by mundane on 2018/3/10 下午6:03
*/

public class EasyDialogFragment extends DialogFragment implements OnClickListener {

private static final String KEY_TITLE = "key_title";
private String mTitle;
private TextView mTvTitle;
private View mBtnCancel;
private View mBtnConfirm;

interface OnClickListener {
void onClick();
}

private OnClickListener mPositiveListener;

private OnClickListener mNegativeListener;

private void setPositiveListener(OnClickListener onClickListener) {
mPositiveListener = onClickListener;
}

private void setNegativeListener(OnClickListener onClickListener) {
mNegativeListener = onClickListener;
}


@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_cancel:
if (mNegativeListener != null) {
mNegativeListener.onClick();
}
break;
case R.id.btn_confirm:
if (mPositiveListener != null) {
mPositiveListener.onClick();
}
break;
}
dismiss();
}

public static final class Builder {
private String title;
private OnClickListener mPositiveListener;
private OnClickListener mNegativeListener;

public Builder() {
title = "";
}

public Builder setTitle(String title) {
this.title = title;
return this;
}

public Builder setPositiveButton(OnClickListener onClickListener) {
mPositiveListener = onClickListener;
return this;
}

public Builder setNegativeButton(OnClickListener onClickListener) {
mNegativeListener = onClickListener;
return this;
}


public DialogFragment build() {
EasyDialogFragment dialogFragment = new EasyDialogFragment();
Bundle bundle = new Bundle();
bundle.putString(KEY_TITLE, title);
dialogFragment.setArguments(bundle);
dialogFragment.setPositiveListener(mPositiveListener);
dialogFragment.setNegativeListener(mNegativeListener);
return dialogFragment;
}
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
mTitle = bundle.getString(KEY_TITLE);
}


@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
// 圆角背景
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
View rootView = inflater.inflate(R.layout.layout_easy_dialogfragment, container, false);
mTvTitle = rootView.findViewById(R.id.tv_title);
mTvTitle.setText(mTitle);
mBtnCancel = rootView.findViewById(R.id.btn_cancel);
mBtnConfirm = rootView.findViewById(R.id.btn_confirm);
mBtnCancel.setOnClickListener(this);
mBtnConfirm.setOnClickListener(this);
return rootView;
}
}

现在在日常的开发中 Director 角色经常会被忽略,这样会相对的减少了构造的步骤而直接使用一个 Builder 来进行对象的组装,最关键的还是 Builder 通常为链式调用,它的每个 setter 方法都返回自身,也就是代码中的 return this, 这样就可以实现链式调用了。

使用EasyDialogFragment:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DialogFragment dialogFragment = new Builder().setTitle("这是标题")
.setPositiveButton(new EasyDialogFragment.OnClickListener() {
@Override
public void onClick() {
Toast.makeText(MainActivity.this, "确定", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton(new EasyDialogFragment.OnClickListener() {
@Override
public void onClick() {
Toast.makeText(MainActivity.this, "取消", Toast.LENGTH_SHORT).show();
}
})
.build();
dialogFragment.show(getSupportFragmentManager(), "");

效果:

总结

最后总结一下builder模式的优缺点
优点:

  1. 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节
  2. 建造者独立, 容易扩展
  3. 链式调用使得代码更简洁、易懂

缺点:
会产生多余的builder对象以及Director对象, 消耗内存

最后

最后发现了一个自动生成builder模式的插件 InnerBuilder
详细可以看这篇
本文代码地址:
https://github.com/mundane799699/AndroidProjects/tree/master/BuildDesignPattern

0%