设计模式之builder模式
文章首发于个人博客
参考:
- 经典 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…
更多相关文章
- dex2oat的原理及慢的原因
- 我的Android进阶之旅------>android Toast大全(五种情形)建立属于
- 一段android跳转到web界面的代码
- android task与back stack 开发文档翻译 - 2
- Android(安卓)Studio中svn的使用(全部在这里)
- 请求网络数据后更新视图
- [置顶] Android(安卓)Studio快捷键
- Android(安卓)Studio 快捷键 (Mac OS X)
- Android观察者模式实例分析