当Android遇上设计模式之建造者(Builder)模式
转载请声明出处:https://blog.csdn.net/AndrExpert/article/details/79665082
众所周知,无论是大部分书籍还是博客,对设计模式的介绍也仅表现在简单的java举例层面,虽然是看懂了,但是在开发的过程中就是不知道如何应用到项目中,时间久了也容易忘记。因此从今天开始,我计划从Android开发者角度写一些关于设计模式系列文章,希望通过这个系列的文章我们不仅能够理解这些设计模式的原理,更能够将其应用到我们的实际项目中。
一、Builder模式原理剖析
Builder,译为”建造者”,我们可以很容易联想想到建筑工人建房子,其大概的流程由打地基、修建主体框架、装修等部分组成,且每个部分是由不同的建筑工人完成。虽说不同的建筑工人修建的风格不一样,但是只要按照上述的修建流程,就能够得到一栋完整的房子,这就是传说中的Builder模式!
1. Builder模式
所谓建造者(Builder)模式,即将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。通俗一点理解,该模式是为了将构建复杂对象的过程和它的部件解耦,使用构建过程和部件都可以自由扩展,同时对外部(客户端)隐藏实现细节,客户端只需调用相同的构建过程,就可以得到不同的产品(对象)。
根据Builder模式理论,以上述建房子为例,房子为要构建的复杂对象(Product),”打地基-修建主体框架-装修”为构建过程(Director),”打地基”、”修建主体框架”、”装修”为构建对象的部件(Builder),可以看出构建过程与它的部件是相互独立的,而对于业主来说,他无法(无需)知道修建的整个流程和每个部分的具体细节,只需要事先告诉建筑工人哪里用什么材料或怎么设计,就可以得到他预想中的房子,并且每个部分建筑工人还可以加入他自己的idea,业主最终可以获取风格不同的房子。
2. UML类图
根据Builder模式的定义,可以得到该模式的UML类图,有以下几个角色:
- (1) Builder:抽象Builder类,规定产品有那些部件,且部件的具体实现由其子类实现;
- (2) ConcreteBuilder:Builder子类,产品部件的具体实现;
- (3) Director:规定构建对象的过程;
- (4) Product:具体产品类;
从UML类图可知,Builder和Director为聚合关系,Director类持有Builder的引用用于调用创建部件相关方法,而Builder是可以脱离Director而单独存在的,即它们是相互独立的;ConcreteBuilder和Builder为继承关系,前者继承于后者,且是后者的具体实现;ConcreteBuilder依赖于Product,ConcreteBuilder持有Product的引用才能完成其工作。
3. 时序图
二、Android项目实战
在Android源码中,建造者(Builder)模式应用非常广泛,比较经典的就是AlertDialog内部实现,它就是通过Builder对象来组装Dialog的各个部分,如title、Message、Button等,将Dialog的构造和表示进行分离。因此,为了进一步理解Builder模式,我们在不看AlertDialog源码的情况下,尝试着根据Builder模式的原理来实现Dialog的创建。该项目的UML类图如下:
- Builder,建造者抽象类。
Builder类是建造者抽象类,该类规定了一个Dialog对象由图标、标题、内容、Button操作部件组成,并提供相关的抽象方法。代码如下:
/**Builder,建造一个Dialog对象的各个部分(部件)的抽象接口 * Created by jianddongguo on 2018/3/4. */public abstract class Builder { // 图标 public abstract void setIcon(int id); // 标题 public abstract void setTitle(String title); // 内容 public abstract void setMessage(String[] content); // 操作 public abstract void setOnDialogClickListener(String confirmText,String cancelText, Dialog.OnDialogClickListener listener); // 创建Dialog public abstract Dialog create();}
- AlertDialogBuilder,建造者具体类
继承于Builder,用于创建一个Dialog对象各个组成部分的具体实现。比如我们需要创建的Dialog类型为提示对话框(AlertDialog),那么对话框的ContentView应该是一个TextView,再给该TextView设置文本。也就是说,随着创建Dialog类型不同,建造者具体类可以不同,比如我们需要创建一个列表对话框,那么ContentView应该是一个ListView,该类的名字改为ListDialogBuilder。
/** * Builder具体实现类,用于建造AlertDialog各部件 * Created by jianddongguo on 2018/3/4. */public class AlertDialogBuilder extends Builder { private Dialog mDialog = new AlertDialog(); private Context mContext; public AlertDialogBuilder(Context ctx) { mContext = ctx.getApplicationContext(); } @Override public void setIcon(int id) { mDialog.setIcon(id); } @Override public void setTitle(String title) { mDialog.setTitle(title); } @Override public void setMessage(String[] content) { if (content != null || content.length > 0) { TextView tv = new TextView(mContext); tv.setText(content[0]); tv.setTextSize(16); mDialog.setContentView(tv); } } @Override public void setOnDialogClickListener(String confirmText, String cancelText, Dialog.OnDialogClickListener listener) { mDialog.setClickButton(confirmText, cancelText,listener); } @Override public Dialog create() { return mDialog; }}
- Director
顾名思义,Director类是这整个故事的”导演”,虽然整个”剧组”的各个部分已经全部就绪,指挥棒(Builder)也已经交到Director手中,”导演”就会根据剧本安排好每个情节流程开始拍摄(construct),最终将所有情节根据预设好的剧本(流程)拍摄成电影(Dialog)。
/**Director层, * Created by jianddongguo on 2018/3/4. */public class Director { private Builder mBuilder; public Director(Builder builder) { this.mBuilder = builder; } public void construct(int iconId, String title, String[] content, String confirmText, String cancelText, Dialog.OnDialogClickListener listener) { mBuilder.setIcon(iconId); mBuilder.setTitle(title); mBuilder.setMessage(content); mBuilder.setOnDialogClickListener(confirmText,cancelText,listener); }}
- Dialog,产品(抽象)类
/**Product层,产品类 * Created by jianddongguo on 2018/3/4. */public abstract class Dialog { protected int mIcon; protected String mTitle; protected View mContentView; protected String mCancelText; protected String mConfirmText; protected Context mCtx; protected OnDialogClickListener mClickListener; public interface OnDialogClickListener { void onCancle(); void onConfirm(String content); } public void setIcon(int icon) { this.mIcon = icon; } public void setTitle(String title) { this.mTitle = title; } public void setContentView(View view) { this.mCtx = view.getContext(); this.mContentView = view; } public void setClickButton(String confirmText,String cancelText,OnDialogClickListener listener) { this.mCancelText = cancelText; this.mConfirmText = confirmText; this.mClickListener = listener; } public abstract void show(); public abstract void dismiss();}
- AlertDialog
继承于Dialog的产品类,它不会理会一个Dialog产品创建流程和每个部分的实现具体细节,只需要关注自己提供创建所需信息即可。代码如下:
/** * Dialog,产品具体实现类 * Created by jianddongguo on 2018/3/4. */public class AlertDialog extends Dialog { private WindowManager mWindowManager; private View mRootView; @Override public void show() { // 使用WindowManager添加View到Window mWindowManager = (WindowManager) mCtx.getSystemService(Context.WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT , WindowManager.LayoutParams.WRAP_CONTENT); params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.type = WindowManager.LayoutParams.TYPE_PHONE; params.width = 800; params.height = 600; params.gravity = Gravity.CENTER; mRootView = LayoutInflater.from(mCtx).inflate(R.layout.layout_dialog, null); ImageView ivIcon = (ImageView) mRootView.findViewById(R.id.iv_dialog_icon); ivIcon.setImageResource(mIcon); TextView tvTitle = (TextView) mRootView.findViewById(R.id.tv_dialog_title); tvTitle.setText(mTitle); LinearLayout contentLayout = (LinearLayout) mRootView.findViewById(R.id.llayout_content); contentLayout.addView(mContentView); Button btnCancel = (Button) mRootView.findViewById(R.id.btn_cancel); btnCancel.setText(mCancelText); btnCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mClickListener != null) { mClickListener.onCancle(); } dismiss(); } }); Button btnConfirm = (Button) mRootView.findViewById(R.id.btn_confirm); btnConfirm.setText(mConfirmText); btnConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mContentView instanceof TextView) { if(mClickListener != null) { mClickListener.onConfirm((String) ((TextView) mContentView).getText()); } } dismiss(); } }); mWindowManager.addView(mRootView, params); } @Override public void dismiss() { if(mWindowManager != null) { mWindowManager.removeView(mRootView); } }}
客户端调用(Activity)
// 实例化Builder构建对象AlertDialogBuilder alertBuidler = new AlertDialogBuilder(this);// 指定构建流程,开始构建Director alertDirect = new Director(alertBuidler);alertDirect.construct(R.mipmap.ic_launcher, "AlertDialog标准版", new String[]{"这是标准版测试"}, "确认", "取消", new Dialog.OnDialogClickListener() { @Override public void onCancle() { showToast("取消操作"); } @Override public void onConfirm(String content) { showToast("确认,打印内容:"+content); } });// 得到产品alertBuidler.create().show();
至此,一个标准的Builder模式流程实战就完成了。由于Builder模式构建过程与部件高度解耦的特点,我们可以再不修改Director类任何代码的情况下,通过创建不同的Buidler子类,来创建风格不同的Dialog。比如,我需要创建一个列表对话框(ListDialog),只需要创建一个ListDialogBuilder子类(继承于Builder)重新设计对话框组成部分就可以实现。当然,实际应用中可能不会按照这种标准的流程来使用Builder模式,会对其进行简化,通常Builder类会直接承担Director、Builder、ConcreteBuilder的责任。通过对上述项目重构,你会发现重构后的AlertDialog同Android源码中的实现结构是如此相似,代码如下:
public class AlertDialog2 { protected int mIcon; protected String mTitle; protected String mCancelText; protected String mConfirmText; protected Context mCtx; protected Dialog.OnDialogClickListener mClickListener; private View mContentView; private WindowManager mWindowManager; private View mRootView; public AlertDialog2(Context ctx) { this.mCtx = ctx.getApplicationContext(); } public interface OnDialogClickListener { void onCancle(); void onConfirm(String content); } public void setIcon(int icon) { this.mIcon = icon; } public void setTitle(String title) { this.mTitle = title; } public void setContentView(View view) { this.mContentView = view; } public void setClickButton(String confirmText, String cancelText, Dialog.OnDialogClickListener listener) { this.mCancelText = cancelText; this.mConfirmText = confirmText; this.mClickListener = listener; } public void show() { // 使用WindowManager添加View到Window mWindowManager = (WindowManager) mCtx.getSystemService(Context.WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.MATCH_PARENT , WindowManager.LayoutParams.WRAP_CONTENT); params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.type = WindowManager.LayoutParams.TYPE_PHONE; params.width = 800; params.height = 600; params.gravity = Gravity.CENTER; mRootView = LayoutInflater.from(mCtx).inflate(R.layout.layout_dialog, null); ImageView ivIcon = (ImageView) mRootView.findViewById(R.id.iv_dialog_icon); ivIcon.setImageResource(mIcon); TextView tvTitle = (TextView) mRootView.findViewById(R.id.tv_dialog_title); tvTitle.setText(mTitle); LinearLayout contentLayout = (LinearLayout) mRootView.findViewById(R.id.llayout_content); contentLayout.addView(mContentView); Button btnCancel = (Button) mRootView.findViewById(R.id.btn_cancel); btnCancel.setText(mCancelText); btnCancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mClickListener != null) { mClickListener.onCancle(); } dismiss(); } }); Button btnConfirm = (Button) mRootView.findViewById(R.id.btn_confirm); btnConfirm.setText(mConfirmText); btnConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mContentView instanceof TextView) { if (mClickListener != null) { mClickListener.onConfirm((String) ((TextView) mContentView).getText()); } } dismiss(); } }); mWindowManager.addView(mRootView, params); } public void dismiss() { if (mWindowManager != null) { mWindowManager.removeView(mRootView); } } /** * 建造者,AlertDialog内部类 * 此处采用链式调用 */ public static class Builder { private Context mCtx; private AlertDialog2 mDialog; public Builder(Context ctx) { this.mCtx = ctx; mDialog = new AlertDialog2(mCtx); } public Builder setIcon(int id) { mDialog.setIcon(id); return this; } public Builder setTitle(String title) { mDialog.setTitle(title); return this; } public Builder setMessage(String[] content) { if (content != null || content.length > 0) { TextView tv = new TextView(mCtx); tv.setText(content[0]); tv.setTextSize(16); mDialog.setContentView(tv); } return this; } public Builder setOnDialogClickListener(String confirmText, String cancelText, Dialog.OnDialogClickListener listener) { mDialog.setClickButton(confirmText, cancelText, listener); return this; } public AlertDialog2 create() { return mDialog; } }}
最后说一句:
Builder模式的最大优势是构建过程与组成解耦,两者可自由扩展,且对外部隐藏实现细节。如果需要实现不同的内部细节,只需要创建一个新的Builder子类即可。
基于此,常用的场合有:
(1) 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值时;
(2) 当建造过程相对稳定,但对象内部的构建通常面临着复杂的变化;
本项目Github地址
设计模式Android实战汇总
更多相关文章
- No.11 使用firewall配置的防火墙策略的生效模式
- 【Android(安卓)源码解析】浅谈DecorView与ViewRootImpl
- Android(安卓)IPC机制(二)用Messenger进行进程间通信
- Android应用资源---绘制资源类型(Drawable)(一)
- Android中RxJava+Retrofit+MVP模式的整合(1)—目录设计
- android Activity之间数据传递 Parcelable和Serializable接口的
- android Task(任务)的简单理解
- Android(安卓)商业模式里的真相与谎言
- android 访问https服务器