Android源码设计模式(一) -- 面向对象的六大原则
Android源码设计模式(二)-- 应用最广的模式:单例模式
Android源码设计模式(三)-- 自由扩展你的项目:Builder 模式
Android源码设计模式(四)-- 时势造英雄:策略模式
Android源码设计模式(五)-- 使编程更有灵活性:责任链模式
Android源码设计模式(六)— 编程好帮手:代理模式
Android源码设计模式(七)— 解决、解耦的钥匙 — 观察者模式
简书 MD 语法不识别 [TOC] ,也不会根据标题行(#) 来插入目录,作为每次看资料喜欢先看目录把握总体的我来说,很不习惯,查找跳转也不方便,所以,如果看到文章没有目录预览的,请安装脚本:简书目录脚本地址
一、Builder 模式的定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
二、Builder 模式的使用场景
- 相同的方法,不同的执行顺序,产生不同的事件结果时;
- 多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时;
- 产品类非常复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时候使用建造者模式非常合适;
- 当初始化一个对象特别复杂,如参数多,且很多参数都具有默认值时。
三、Builder 模式的 UML 类图
builder-uml.png角色介绍
- Product 产品类 : 产品的抽象类。
- Builder:抽象 Builder 类, 规范产品的组建,一般是由子类实现具体的组件过程。
- ConcreteBuilder : 具体的 Builder 类。
- Director : 统一组装过程(可省略)。
四、Builder 模式的简单实现
简单实现的介绍
电脑的组装过程较为复杂,步骤繁多,但是顺序却是不固定的,为了易于理解,我们把计算机组装过程简化为构建主机,设置操作系统,设置显示器 3 个部分,然后通过 Director 和具体的 Builder 来构建计算机对象。下面我们以组装电脑为例来演示一下简单且经典的builder 模式。
package com.dp.example.builder;
/**
* Computer产品抽象类, 为了例子简单, 只列出这几个属性
* @author mrsimple
*/
public abstract class Computer {
protected int mCpuCore = 1;
protected int mRamSize = 0;
protected String mOs = "Dos";
protected Computer() {
}
// 设置CPU核心数
public abstract void setCPU(int core);
// 设置内存
public abstract void setRAM(int gb);
// 设置操作系统
public abstract void setOs(String os);
@Override
public String toString() {
return "Computer [mCpuCore=" + mCpuCore + ", mRamSize=" + mRamSize
+ ", mOs=" + mOs + "]";
}
}
package com.dp.example.builder;
/**
* Apple电脑
*/
public class AppleComputer extends Computer {
protected AppleComputer() {
}
@Override
public void setCPU(int core) {
mCpuCore = core;
}
@Override
public void setRAM(int gb) {
mRamSize = gb;
}
@Override
public void setOs(String os) {
mOs = os;
}
}
package com.dp.example.builder;
/**
* builder抽象类
*
*/
public abstract class Builder {
// 设置CPU核心数
public abstract void buildCPU(int core);
// 设置内存
public abstract void buildRAM(int gb);
// 设置操作系统
public abstract void buildOs(String os);
// 创建Computer
public abstract Computer create();
}
package com.dp.example.builder;
public class ApplePCBuilder extends Builder {
private Computer mApplePc = new AppleComputer();
@Override
public void buildCPU(int core) {
mApplePc.setCPU(core);
}
@Override
public void buildRAM(int gb) {
mApplePc.setRAM(gb);
}
@Override
public void buildOs(String os) {
mApplePc.setOs(os);
}
@Override
public Computer create() {
return mApplePc;
}
}
package com.dp.example.builder;
public class Director {
Builder mBuilder = null;
/**
* @param builder
*/
public Director(Builder builder) {
mBuilder = builder;
}
/**
* 构建对象
* @param cpu
* @param ram
* @param os
*/
public void construct(int cpu, int ram, String os) {
mBuilder.buildCPU(cpu);
mBuilder.buildRAM(ram);
mBuilder.buildOs(os);
}
}
/**
* 经典实现较为繁琐
* @author mrsimple
*/
public class Test {
public static void main(String[] args) {
// 构建器
Builder builder = new ApplePCBuilder();
// Director
Director pcDirector = new Director(builder);
// 封装构建过程, 4核, 内存2GB, Mac系统
pcDirector.construct(4, 2, "Mac OS X 10.9.1");
// 构建电脑, 输出相关信息
System.out.println("Computer Info : " + builder.create().toString());
}
}
通过 Builder 来构建产品对象, 而 Director 封装了构建复杂产品对象对象的过程,不对外隐藏构建细节。Builder 和 Director 一起将复杂对象的构建和表示分离,使得同样的构建过程可以创建不同的对象。
Director 在实际开发中经常被省略,而是直接用 Builder 来进行对象的组装,这个 Builder 通常为链式调用,它的关键点是每个 setter 方法都返回自身,也就是 return this,这样就使得 setter 方法可以链式调用。
new Builder().setA("A").setB("B").build();
通过这种方式不仅去除了 Director 角色,整个结构更加简单,也能对 Product 对象的组装过程有更加精细的控制。
省去 Director 角色的 Builder,我之前写过一篇 PopupWindow 的封装,就是采用 Builder 模式。MPopupWindow
五、 Android 源码中的 Builder 模式实现
在 Android 源码中,我们最常用到的 Builder 模式就是 AlertDialog.Builder, 使用该 Builder 来构建复杂的AlertDialog 对象。简单示例如下 :
//显示基本的AlertDialog
private void showDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon);
builder.setTitle("Title");
builder.setMessage("Message");
builder.setPositiveButton("Button1",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("点击了对话框上的Button1");
}
});
builder.setNeutralButton("Button2",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("点击了对话框上的Button2");
}
});
builder.setNegativeButton("Button3",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setTitle("点击了对话框上的Button3");
}
});
builder.create().show(); // 构建AlertDialog, 并且显示
}
result.png
下面我们看看 AlertDialog 的相关源码 :
// AlertDialog
public class AlertDialog extends Dialog implements DialogInterface {
// Controller, 接受Builder成员变量P中的各个参数
private AlertController mAlert;
// 构造函数
protected AlertDialog(Context context, int theme) {
this(context, theme, true);
}
// 4 : 构造AlertDialog
AlertDialog(Context context, int theme, boolean createContextWrapper) {
super(context, resolveDialogTheme(context, theme), createContextWrapper);
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
}
// 实际上调用的是mAlert的setTitle方法
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
mAlert.setTitle(title);
}
// 实际上调用的是mAlert的setCustomTitle方法
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
// AlertDialog其他的代码省略
// ************ Builder为AlertDialog的内部类 *******************
public static class Builder {
// 1 : 存储AlertDialog的各个参数, 例如title, message, icon等.
private final AlertController.AlertParams P;
// 属性省略
/**
* Constructor using a context for this builder and the {@link AlertDialog} it creates.
*/
public Builder(Context context) {
this(context, resolveDialogTheme(context, 0));
}
public Builder(Context context, int theme) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, theme)));
mTheme = theme;
}
// Builder的其他代码省略 ......
// 2 : 设置各种参数
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
public Builder setMessage(CharSequence message) {
P.mMessage = message;
return this;
}
public Builder setIcon(int iconId) {
P.mIconId = iconId;
return this;
}
public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
P.mPositiveButtonText = text;
P.mPositiveButtonListener = listener;
return this;
}
public Builder setView(View view) {
P.mView = view;
P.mViewSpacingSpecified = false;
return this;
}
// 3 : 构建AlertDialog, 传递参数
public AlertDialog create() {
// 调用new AlertDialog构造对象, 并且将参数传递个体AlertDialog
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
// 5 : 将P中的参数应用的dialog中的mAlert对象中
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
}
}
可以看到,通过 Builder 来设置 AlertDialog 中的 title, message, button 等参数, 这些参数都存储在类型为AlertController.AlertParams 的成员变量 P 中,AlertController.AlertParams 中包含了与之对应的成员变量。在调用 Builder 类的 create 函数时才创建 AlertDialog, 并且将 Builder 成员变量 P 中保存的参数应用到 AlertDialog的 mAlert 对象中,即 P.apply(dialog.mAlert) 代码段。我们看看 apply 函数的实现 :
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) {
dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText,
mPositiveButtonListener, null);
}
if (mNegativeButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText,
mNegativeButtonListener, null);
}
if (mNeutralButtonText != null) {
dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText,
mNeutralButtonListener, null);
}
if (mForceInverseBackground) {
dialog.setInverseBackgroundForced(true);
}
// 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);
}
}
}
在 apply 函数中,只是将 AlertParams 参数设置到 AlertController 中。例如,将标题设置到 Dialog 对应的标题识图中,将 Message 设置到内容视图中等。当我们获取到 AlertDialog 对象后,通过 show 函数就可以显示这个对话框。 AlertDialog 中相关代码如下:
//显示Dialog
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();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
//5、将mDecor添加到mWindowManager中
mWindowManager.addView(mDecor, l);
mShowing = true;
//发送一个显示Dialog的消息
sendShowMessage();
} finally {
}
}
在 show 函数中主要做了如下几个事情:
- 通过 dispatchOnCreate 函数来调用 AlertDialog 中的 onCreate 函数。
- 调用 AlertDialog 的 onStart 函数。
- 最后将 Dialog 的 DecorView 添加到 WindowManager 中。
很明显,这就是一系列的典型的生命周期函数。按照惯例,AlertDialog 的内容视图构建按理应该在 onCreate 函数中,我们来看看是不是:
public class AlertDialog extends Dialog implements DialogInterface {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//调用了AlertController的installContent方法
mAlert.installContent();
}
}
在 onCreate 函数中主要调用了 AlertController 的 installContent 方法,Dialog 中的 onCreate 函数只是一个空实现而已,可以忽略他。那么 AlertDialog 的内容视图必然就在 installContent 函数中。
public void installContent() {
/* We use a custom title so never request a window title */
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
int contentView = selectContentView();
mWindow.setContentView(contentView);
setupView();
setupDecor();
}
private int selectContentView() {
if (mButtonPanelSideLayout == 0) {
return mAlertDialogLayout;
}
if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
return mButtonPanelSideLayout;
}
// TODO: use layout hint side for long messages/lists
return mAlertDialogLayout;
}
installContent 函数的代码很少,但极为重要,它调用了 Window 对象的 setContentView,这个 setContentView 就与 Activity 中的一模一样,实际上 Activity 最终也是调用 Window 对象的 setContentView 函数。因此,这里就是设置 AlertDialog 的内容布局,这个布局就是 mAlertDialogLayout 的值。这个值在AlertController 的构造函数中进行了初始化,具体代码如下:
public AlertController(Context context, DialogInterface di, Window window) {
mContext = context;
mDialogInterface = di;
mWindow = window;
mHandler = new ButtonHandler(di);
final TypedArray a = context.obtainStyledAttributes(null,
R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
//AlertController的布局id,也就是alert_dialog.xml布局
mAlertDialogLayout = a.getResourceId(
R.styleable.AlertDialog_layout, R.layout.alert_dialog);
mButtonPanelSideLayout = a.getResourceId(
R.styleable.AlertDialog_buttonPanelSideLayout, 0);
mListLayout = a.getResourceId(
R.styleable.AlertDialog_listLayout, R.layout.select_dialog);
//省略
a.recycle();
}
从 AlertController 的构造函数中可以看到,AlertDialog 的布局资源就是 select_dialog.xml 这个布局,我们直接大致看他的结构如下:
AlertController 结构当通过 Builder 对象的 setTitle、setMessage 等方法设置具体内容时,就是将这些内容填充到对应的视图中。而AlertDialog 也允许你通过 setView 传入内容视图,这个内容视图就是替换掉图中的内容视图(蓝色区域),AlertDialog 预留了一个 costomPanel 区域用来显示用户自定义的内容视图。我们来看看 setupView 函数:
private void setupView() {
final View parentPanel = mWindow.findViewById(R.id.parentPanel);
final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
//1、获取内容区域
final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
//1、获取按钮
final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
// Install custom content before setting up the title or buttons so
// that we can handle panel overrides.
final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel);
//自定义内容视图区域
setupCustomContent(customPanel);
final View customTopPanel = customPanel.findViewById(R.id.topPanel);
final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
// Resolve the correct panels and remove the defaults, if needed.
final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
//2、初始化内容
setupContent(contentPanel);
setupButtons(buttonPanel);
//2、初始化标题
setupTitle(topPanel);
//自定义视图可见性
final boolean hasCustomPanel = customPanel != null
&& customPanel.getVisibility() != View.GONE;
final boolean hasTopPanel = topPanel != null
&& topPanel.getVisibility() != View.GONE;
final boolean hasButtonPanel = buttonPanel != null
&& buttonPanel.getVisibility() != View.GONE;
// Only display the text spacer if we don't have buttons.
if (!hasButtonPanel) {
if (contentPanel != null) {
final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons);
if (spacer != null) {
spacer.setVisibility(View.VISIBLE);
}
}
mWindow.setCloseOnTouchOutsideIfNotSet(true);
}
if (hasTopPanel) {
// Only clip scrolling content to padding if we have a title.
if (mScrollView != null) {
mScrollView.setClipToPadding(true);
}
// Only show the divider if we have a title.
final View divider;
if (mMessage != null || mListView != null || hasCustomPanel) {
divider = topPanel.findViewById(R.id.titleDivider);
} else {
divider = topPanel.findViewById(R.id.titleDividerTop);
}
if (divider != null) {
divider.setVisibility(View.VISIBLE);
}
}
// Update scroll indicators as needed.
if (!hasCustomPanel) {
final View content = mListView != null ? mListView : mScrollView;
if (content != null) {
final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
| (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
content.setScrollIndicators(indicators,
View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
}
}
final TypedArray a = mContext.obtainStyledAttributes(
null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
//设置背景
setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
hasTopPanel, hasCustomPanel, hasButtonPanel);
a.recycle();
}
//自定义内容视图区域
private void setupCustomContent(ViewGroup customPanel) {
final View customView;
//如果用户设置了内容视图,那么将它显示在customPanel的custom布局里面
if (mView != null) {
customView = mView;
} else if (mViewLayoutResId != 0) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
customView = inflater.inflate(mViewLayoutResId, customPanel, false);
} else {
customView = null;
}
final boolean hasCustomView = customView != null;
if (!hasCustomView || !canTextInput(customView)) {
mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
}
if (hasCustomView) {
final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom);
//显示用户设置的视图
custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
if (mViewSpacingSpecified) {
custom.setPadding(
mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
}
if (mListView != null) {
((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0;
}
} else {
customPanel.setVisibility(View.GONE);
}
}
这个 setupView 顾名思义就是初始化 AlertDialog 布局中的各个部分,如标题区域、按钮区域、内容区域等,在该函数调用之后整个 Dialog 的视图内容全部设置完毕。而这些各区域的视图都属于 mAlertDialogLayout 布局中的子元素,Window 对象又关联了 mAlertDialogLayout 的整个布局树,当调用完 setupView 之后整个视图树的数据都填充完毕。当用户调用了 show 函数时,WindosManager 会将 Window 对象的 DecorView (也就是 mAlertDialogLayout 对应的视图),添加到用户的窗口上,并且显示出来。至此,整个 Dialog 就会出现在用户的视野中了。
AlertDialog
- Builder 内部类
- onCreate() 方法调用 AlertController 类 installContent() 方法设置内容布局
AlertDialog.Builder --- Builder、ConcreteBuilder、Dirctor角色
-
初始化 AlertController.AlertParams 对象 p 储存数据 (Title、message、icon 等)。
-
set 方法将数据储存至 AlertController.AlertParams 对象 p。
-
create() 方法储存将 AlertController.AlertParams 对象 p 数据应用至 AlertController 对象 dialog.mAlert。
-
show() 方法调用 AlertDialog 父类 Dialog 的 show() 方法加载布局。
AlertController
- AlertParams内部类
- 接收数据(Title、message、icon等)
- installContent()方法调用Window对象的setContentView函数设置AlertDialog的内容布局,并调用setupView()方法初始化布局
- setupView()初始化布局
AlertController.AlertParams
- 用于储存数据(Title、message、icon等)
- apply()方法将数据传递给AlertDialog类实例化的AlertController对象
在 AlertDialog 的 Builder 模式中并没有看到 Director 角色的出现(其实在很多场景中,Android 并没有按照《设计模式: 可复用面向对象软件的基础》一书中描述的经典模式来实现,而是做了些修改,使得更易于使用)。这里的AlertDialog.Builder 同时扮演了 builder、ConcreteBuilder、Dirctor 的角色,简化了 Builder 模式的设计。
六、总结
优点与缺点
优点
- 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节;
- 建造者独立,容易扩展;
- 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。
缺点
- 会产生多余的 Builder 对象以及 Director 对象,消耗内存;
- 对象的构建过程暴露。
网友评论