Android设计模式之建造者模式(Builder Pattern)
来源:互联网 发布:知乎hexo简书 编辑:程序博客网 时间:2024/05/02 01:54
假如一个对象由许多不同的属性构造,我们想要构造一个我们自己指定特定属性的对象,最简单的方法就是为每种情况提供一个构造函数,我们根据不要的构造函数来得到我们需要的包含了指定属性的对象。我们还是举个例子吧。
一个人有姓名、性别、年龄、身高,体重这五个属性组成,当我们创建一个人这个对象的时候,我们可能有下面这些情况:
1、只希望指定姓名
2、只希望指定性别
3、只希望指定年龄
4、只希望指定身高
5、只希望指定体重
6、只希望指定姓名和性别
7、只希望指定姓名和年龄
8、只希望指定姓名和身高
9、只希望指定姓名和体重
10、只希望指定性别和年龄
11、只希望指定性别和身高
12、……
上面就不一样列举了,就是一个排列组合问题,是不是有些凌乱了,如果一个对象的属性更多,那情况就更多了,显然把所以执行情况的构造函数都写出来不是一个明智的选择,因为想想你要写多少个构造函数,好恐怖,不敢想象。
那我们有没有一种方法来解决这个问题呢?这里我们就要使用建造者模式,它就是单独的来对一个对象进行构造,将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。也就是说它来完成对象的构造过程,并且这个过程可以构造出上面我们所说的所有我们希望得到的对象。
建造模式是将复杂的内部创建封装在内部,对于外部调用的人来说,只需要传入建造者和建造工具,对于内部是如何建造成成品的,调用者无需关心。
针对上面所说的那个包含了5个属性的对象,我们使用构建者模式如何完成,下面我们来看看。
1、定义一个Person类,他包含了所有属性的get,set方法。
public class Person { private String name; private boolean sex; private int age; private float height; private float weight; public Person(String name, boolean sex, int age, float height, float weight) { this.name = name; this.sex = sex; this.age = age; this.height = height; this.weight = weight; }}
2、创建一个Builder类
public class Builder { private String name; private boolean sex; private int age; private float height; private float weight; public Builder setName(String name) { this.name = name; return this; } public Builder setSex(boolean sex) { this.sex = sex; return this; } public Builder setAge(int age) { this.age = age; return this; } public Builder setHeight(float height) { this.height = height; return this; } public Builder setWeight(float weight) { this.weight = weight; return this; } public Person create() { return new Person(name, sex, age, height, weight); }}
上面我们就写好了这个构造过程了。现在就可以根据我们的需要来得到任何我们想要的对象。
Builder builder = new Builder();builder.setName("Mirhunana");builder.setAge(23);Perons person = builder.create();
上面我们就得到了一个我们想要的对象,很方便,很简单。
上面就是构建者的基本思想,现实我们的使用的时候,可能会做出不同的变形,但是基本思想是不变的。下面我们来讲讲完整的构建者模式。
如下图所示。
它分为抽象建造者(Builder)角色、具体建造者(ConcreteBuilder)角色、导演者(Director)角色、产品(Product)角色四个角色。
抽象建造者(Builder)角色:给 出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者 (ConcreteBuilder)角色。具体建造者类必须实现这个接口所要求的两种方法:一种是建造方法(buildPart1和 buildPart2),另一种是返还结构方法(retrieveResult)。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少 零件,就有多少相应的建造方法。
具体建造者(ConcreteBuilder)角色:担任这个角色的是与应用程序紧密相关的一些类,它们在应用程序调用下创建产品的实例。这个角色要完成的任务包括:1.实现抽象建造者Builder所声明的接口,给出一步一步地完成创建产品实例的操作。2.在建造过程完成后,提供产品的实例。
导演者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。应当指出的是,导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者角色。
产品(Product)角色:产品便是建造中的复杂对象。一般来说,一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的接口,而完全可以是不相关联的。
下面为了说明这个完整的过程,我们对上面我们写的那个构建者模式进行改进。
1、产品类Product
就是上面的Person
2、抽象建造者类Builder,就是上面的Builder的接口,目的就是为了为构造者提供统一的接口
public interface Builder { public Builder setName(String name); public Builder setSex(boolean sex); public Builder setAge(int age); public Builder setHeight(float height); public Builder setWeight(float weight); public Person create();}
3、 具体建造者类ConcreteBuilder,就是前面的Builder,只是它实现了一个共同的Builder接口
public class ConcreteBuilder implements Builder { private String name; private boolean sex; private int age; private float height; private float weight; public Builder setName(String name) { this.name = name; return this; } public Builder setSex(boolean sex) { this.sex = sex; return this; } public Builder setAge(int age) { this.age = age; return this; } public Builder setHeight(float height) { this.height = height; return this; } public Builder setWeight(float weight) { this.weight = weight; return this; } public Person create() { return new Person(name, sex, age, height, weight); }}
4、导演者类Director,它就是操作builder对象的
public class Director { private Builder builder; public Director(Builder builder){ this.builder = builder; } public void construct(String name, boolean sex, int age, float height, float weight) { builder.setName(name); builder.setSex(sex); builder.setAge(age); builder.setHeight(height); builder.setWeight(weight); }}
5、客户端代码
public class Test { public static void main(String[] args) { Builder builder = new ConcreteBuilder(); Director pcDirector = new Director(builder); pcDirector.construct("Mirhunana", true, 23, 180, 100); Person person = builder.create(); }}
下面我们来看看Android中的构建者模式吧,最经典的就是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, 并且显示}
很显然AlertDialog内部有一个Builder类,它就是用来根据我们的需要构造AlertDialog的。
下面我们来看看这个Builder类。
public static class Builder { private final AlertController.AlertParams P; private int mTheme; 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; } public Context getContext() { return P.mContext; } public Builder setTitle(int titleId) { P.mTitle = P.mContext.getText(titleId); return this; } public Builder setTitle(CharSequence title) { P.mTitle = title; return this; } public Builder setCustomTitle(View customTitleView) { P.mCustomTitleView = customTitleView; return this; } public Builder setMessage(int messageId) { P.mMessage = P.mContext.getText(messageId); return this; } public Builder setMessage(CharSequence message) { P.mMessage = message; return this; } public Builder setIcon(int iconId) { P.mIconId = iconId; return this; } public Builder setIcon(Drawable icon) { P.mIcon = icon; return this; } public Builder setIconAttribute(int attrId) { TypedValue out = new TypedValue(); P.mContext.getTheme().resolveAttribute(attrId, out, true); P.mIconId = out.resourceId; return this; } public Builder setPositiveButton(int textId, final OnClickListener listener) { P.mPositiveButtonText = P.mContext.getText(textId); P.mPositiveButtonListener = listener; return this; } public Builder setPositiveButton(CharSequence text, final OnClickListener listener) { P.mPositiveButtonText = text; P.mPositiveButtonListener = listener; return this; } public Builder setNegativeButton(int textId, final OnClickListener listener) { P.mNegativeButtonText = P.mContext.getText(textId); P.mNegativeButtonListener = listener; return this; } public Builder setNegativeButton(CharSequence text, final OnClickListener listener) { P.mNegativeButtonText = text; P.mNegativeButtonListener = listener; return this; } public Builder setNeutralButton(int textId, final OnClickListener listener) { P.mNeutralButtonText = P.mContext.getText(textId); P.mNeutralButtonListener = listener; return this; } public Builder setNeutralButton(CharSequence text, final OnClickListener listener) { P.mNeutralButtonText = text; P.mNeutralButtonListener = listener; return this; } public Builder setCancelable(boolean cancelable) { P.mCancelable = cancelable; return this; } public Builder setOnCancelListener(OnCancelListener onCancelListener) { P.mOnCancelListener = onCancelListener; return this; } public Builder setOnDismissListener(OnDismissListener onDismissListener) { P.mOnDismissListener = onDismissListener; return this; } public Builder setOnKeyListener(OnKeyListener onKeyListener) { P.mOnKeyListener = onKeyListener; return this; } public Builder setItems(int itemsId, final OnClickListener listener) { P.mItems = P.mContext.getResources().getTextArray(itemsId); P.mOnClickListener = listener; return this; } public Builder setItems(CharSequence[] items, final OnClickListener listener) { P.mItems = items; P.mOnClickListener = listener; return this; } public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) { P.mAdapter = adapter; P.mOnClickListener = listener; return this; } public Builder setCursor(final Cursor cursor, final OnClickListener listener, String labelColumn) { P.mCursor = cursor; P.mLabelColumn = labelColumn; P.mOnClickListener = listener; return this; } public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems, final OnMultiChoiceClickListener listener) { P.mItems = P.mContext.getResources().getTextArray(itemsId); P.mOnCheckboxClickListener = listener; P.mCheckedItems = checkedItems; P.mIsMultiChoice = true; return this; } public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, final OnMultiChoiceClickListener listener) { P.mItems = items; P.mOnCheckboxClickListener = listener; P.mCheckedItems = checkedItems; P.mIsMultiChoice = true; return this; } public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, final OnMultiChoiceClickListener listener) { P.mCursor = cursor; P.mOnCheckboxClickListener = listener; P.mIsCheckedColumn = isCheckedColumn; P.mLabelColumn = labelColumn; P.mIsMultiChoice = true; return this; } public Builder setSingleChoiceItems(int itemsId, int checkedItem, final OnClickListener listener) { P.mItems = P.mContext.getResources().getTextArray(itemsId); P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mIsSingleChoice = true; return this; } public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, final OnClickListener listener) { P.mCursor = cursor; P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mLabelColumn = labelColumn; P.mIsSingleChoice = true; return this; } public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) { P.mItems = items; P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mIsSingleChoice = true; return this; } public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listener) { P.mAdapter = adapter; P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mIsSingleChoice = true; return this; } public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) { P.mOnItemSelectedListener = listener; return this; } public Builder setView(View view) { P.mView = view; P.mViewSpacingSpecified = false; return this; } public Builder setInverseBackgroundForced(boolean useInverseBackground) { P.mForceInverseBackground = useInverseBackground; return this; } public AlertDialog create() { final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true); } dialog.setOnCancelListener(P.mOnCancelListener); dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); } return dialog; } public AlertDialog show() { AlertDialog dialog = create(); dialog.show(); return dialog; }}
它的做法就是:
1、在构造函数里面创建一个AlertParams对象用来缓存AlertDialog的所有属性,另外单独定义一个theme来缓存AlertDialog的主题。
public Builder(Context context, int theme) { P = new AlertController.AlertParams(new ContextThemeWrapper( context, resolveDialogTheme(context, theme))); mTheme = theme;}
2、我们可以通过一系类的set函数,主要就是把我们设置的属性缓存到AlertParams这个对象里面。
3、调用create函数,它的操作就是创建一个AlertDialog对象,然后把AlertParams里面缓存的属性全部应用到AlertDialog上面去。我们来看看它的具体操作。
(1) 创建AlertDialog对象
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
(2) 把缓存的属性全部设置到AlertDialog上面去
P.apply(dialog.mAlert);dialog.setCancelable(P.mCancelable);if (P.mCancelable) { dialog.setCanceledOnTouchOutside(true);}dialog.setOnCancelListener(P.mOnCancelListener);dialog.setOnDismissListener(P.mOnDismissListener);if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener);}
首先我们来看看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); } } /* dialog.setCancelable(mCancelable); dialog.setOnCancelListener(mOnCancelListener); if (mOnKeyListener != null) { dialog.setOnKeyListener(mOnKeyListener); } */}
可以看到AlertParams的这个函数就是把自己内部缓存的属性值全部设置到了AlertDialog里面。
上面的思想跟我们前面将的基本相同,唯一的不同就是,它里面创建了一个属性容器AlertParams,用来存放AlertDialog所有的属性,最后会将这些属性应用到AlertDialog身上,前面我们将的是直接在Builder内部来操作Product对象,也相当于这里的AlertDialog。
另外一个地方也用到了这种构建者模式,那就是我们在创建自定义的Notification的时候。
具体我们来看看Notification里面的setLatestEventInfo函数。
public void setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { Notification.Builder builder = new Notification.Builder(context); // First, ensure that key pieces of information that may have been set directly // are preserved builder.setWhen(this.when); builder.setSmallIcon(this.icon); builder.setPriority(this.priority); builder.setTicker(this.tickerText); builder.setNumber(this.number); builder.mFlags = this.flags; builder.setSound(this.sound, this.audioStreamType); builder.setDefaults(this.defaults); builder.setVibrate(this.vibrate); // now apply the latestEventInfo fields if (contentTitle != null) { builder.setContentTitle(contentTitle); } if (contentText != null) { builder.setContentText(contentText); } builder.setContentIntent(contentIntent); builder.buildInto(this);}
可以看到我们设置Notification属性,它也是通过构建者模式,它的做法又有些不同,就是它直接把Notification属性都缓存在Builder对象里面,然后通过builder.buildInto(this)把builder里面的属性应用到this上,这里的this就是Notification对象本身,因为setLatestEventInfo是它的一个成员函数。
我们可以来大概看看Buidler对象里面的属性。
public static class Builder { private static final int MAX_ACTION_BUTTONS = 3; private Context mContext; private long mWhen; private int mSmallIcon; private int mSmallIconLevel; private int mNumber; private CharSequence mContentTitle; private CharSequence mContentText; private CharSequence mContentInfo; private CharSequence mSubText; private PendingIntent mContentIntent; private RemoteViews mContentView; private PendingIntent mDeleteIntent; private PendingIntent mFullScreenIntent; private CharSequence mTickerText; private RemoteViews mTickerView; private Bitmap mLargeIcon; private Uri mSound; private int mAudioStreamType; private long[] mVibrate; private int mLedArgb; private int mLedOnMs; private int mLedOffMs; private int mDefaults; private int mFlags; private int mProgressMax; private int mProgress; private boolean mProgressIndeterminate; private ArrayList<String> mKindList = new ArrayList<String>(1); private Bundle mExtras; private int mPriority; private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); private boolean mUseChronometer; private Style mStyle; private boolean mShowWhen = true; ...... ...... }
从这里我们就可以看到,确实他内部定义了很多成员变量来存放Notification的属性,最后它会通过builder.buildInto(this)函数把这些属性应用到Notificaition身上,这样达到了为Notificaiton设置属性的目的。我们来看看buildInto函数。
public Notification buildInto(Notification n) { build().cloneInto(n, true); return n;}
它内部通过build函数来来创建一个Notification对象,然后把它里面缓存的属性应用到这个Notification上面,最后通过cloneInto把创建的这个Notification对象复制给传进来的这个Notification,也就是我们真正的Notification,这样最终就把所有的属性应用到我们的Notification上面。
1、Builder里面的build()创建一个Notification,并且把Builder里面缓存的属性都应用到这个Notification上面。
2、Builder里面的cloneInto函数,把我们创建的Notificaiton复制给传进来的Notification,这样我们的Notification就拥有这些属性。
具体的源码这里就不展示了,可以看看自定义Notification和Toast这篇文章,
我们可以看到Builder构建者的使用非常灵活,但是思路和思想确实一致的,我们关键需要把握思想。
参考文章:
Android设计模式源码解析之Builder模式
- Android设计模式之建造者模式(Builder Pattern)
- Android设计模式之建造者模式(builder pattern)
- Android设计模式之建造者模式(Builder Pattern)
- 设计模式----建造者模式(Builder Pattern)
- 设计模式---建造者模式(Builder Pattern)
- 设计模式【建造者模式Builder Pattern】
- Net设计模式实例之建造者模式(Builder Pattern)
- C#设计模式之建造者模式(Builder Pattern)
- JAVA设计模式之 建造者模式【Builder Pattern】
- 设计模式(创建型)之建造者模式(Builder Pattern)
- 设计模式之建造者模式(Builder Pattern)
- 设计模式总结之Builder Pattern(建造者模式)
- C#设计模式之建造者模式(Builder Pattern)
- Net设计模式实例之建造者模式(Builder Pattern)
- 设计模式之建造者模式(Builder Pattern)
- design pattern Builder 建造者设计模式
- Java之建造者模式(Builder Pattern)
- Java之建造者模式-Builder Pattern
- 功能测试要领
- 151024总结
- RecyclerView的基本使用,还有SwipeRefreshLayout(原生的下拉刷新)(1)
- 浅析数据库安全技术
- 使用Android Support Design 控件TabLayout 方便快捷实现选项卡功能
- Android设计模式之建造者模式(Builder Pattern)
- 关于用 ZIP 包 安装 MySql 的详细步骤以及可能的错误
- BZOJ1088: [SCOI2005]扫雷Mine
- i.MX arm 3.12.28 Kernel Configuration
- 关于MVVM模式的简单入门
- 图片随手机姿态变化上下左右移动
- 岛屿的个数(LintCode)
- 51单片机内部E2ROM
- bzoj1631: [Usaco2007 Feb]Cow Party