文章首发于个人博客
参考:
- 经典 Builder / 变种 Builder 模式及自动化生成代码插件
- 变种 Builder 模式:优雅的对象构建方式
- 《Android源码设计模式解析与实战》
- 重学设计模式 -- 建造者模式
- 王者荣耀之「建造者模式」
- 结合 Android 浅谈 Builder 模式
- 人人都会设计模式---建造者模式--Builder
前言
builder模式也叫建造者模式, 也是Android中应用十分广泛的设计模式之一. 举个例子, 比如我们经常使用的框架okhttp和retrofit
OkHttpClient client = new Builder().writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.cache(cache)
.build();
复制代码
Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okhttpClient)
.build();
复制代码
还有Android中AlerDialog
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
/**
* 计算机抽象类, 即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
/**
* 具体的Computer类
*/
public class Macbook extends Computer {
public Macbook() {
}
@Override
public void setOS() {
mOS = "Mac OS X 10.10";
}
}
复制代码
抽象Builder
/**
* 抽象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
/**
* 具体的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
/**
* 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();
}
}
复制代码
测试代码
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());
}
}
复制代码
输出结果:
Computer Info: Computer{mBoard='英特尔主板', mDisplay='Retina 显示器', mOS='Mac OS X 10.10'}
复制代码
可以看出, 经典的 Builder 模式重点在于抽象出对象创建的步骤,并通过调用不同的具体实现类从而得到不同的结果, 但是在创建过程中依然要传入多个参数, 不是很方便, 所以有了变种的Builder模式
变种的Builder模式
目前来说在 Android&Java 开发过程中经典的 Builder 模式使用的较少,一般广泛使用的是他的一个变种
在日常的开发中 Director 角色经常会被忽略,这样会相对的减少了构造的步骤而直接使用一个 Builder 来进行对象的组装
这里我要说的就是一种内部Builder并且能够链式调用的变种
我们就直接进入实战, 模仿AlertDialog写一个dialogfragment, 但是比AlertDialog更简单些.
/**
* 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:
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模式的优缺点
优点:
- 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节
- 建造者独立, 容易扩展
- 链式调用使得代码更简洁、易懂 缺点: 会产生多余的builder对象以及Director对象, 消耗内存
最后
最后发现了一个自动生成builder模式的插件 InnerBuilder, 详细可以看这篇
本文代码地址: github.com/mundane7996…