本文共 12395 字,大约阅读时间需要 41 分钟。
*本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布
建造者模式之前也写了一篇学习笔记,不过那只是很简单的运用,要去看源码,要去看源码还是得在撸一遍设计模式才行啊。不能怂就是干。
建造者模式,在于分工明确,一个抽象建造者,一个指挥者,一个具体的建造者,当然还需要具体的产品。那么我们以一个软件产品为例。技术主管就是抽象建造者,他和产品经理沟通,知道要做一个什么样的产品。而程序猿就是苦逼的体力劳动者,技术主管说咋做你就咋做。而指挥者就是公司的产品经理,负责和用户沟通,了解客户的需求。
那么基本的套路有了,开始撸一个简单的例子出来。
public class Product { public static final int ANDROID = 0; public static final int IOS = 1; private String appName; private String appFuction; private int appSystem; public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public String getAppFuction() { return appFuction; } public void setAppFuction(String appFuction) { this.appFuction = appFuction; } public int getAppSystem() { return appSystem; } public void setAppSystem(int appSystem) { this.appSystem = appSystem; }}这是产品,有app名称,app的功能,和app的操作系统。在来一个技术主管
/** * 技术主管 * Created by Administrator on 2016/12/29. */public abstract class TechManager { public abstract TechManager setAppName(@NonNull String appName); public abstract TechManager setAppFuction(@NonNull String appFuction); public abstract TechManager setAppSystem(@AppSystem int appSystem); public abstract Product build();}在传入的系统时,我用了一个自定义注解,只能传入规定的参数,详情见:
/** *程序猿。IOS和ANDROID,后台都会的全栈选手 * Created by Administrator on 2016/12/29. */public class Progremer extends TechManager{ private Product product; private InnerProduct innerProduct = new InnerProduct(); @Override public TechManager setAppName(@NonNull String appName) { innerProduct.setAppName(appName); return this; } @Override public TechManager setAppFuction(@NonNull String appFuction) { innerProduct.setAppFuction(appFuction); return this; } @Override public TechManager setAppSystem(@AppSystem int appSystem) { innerProduct.setAppSystem(appSystem); return this; } private class InnerProduct{ private String appName; private String appFuction; private int appSystem; public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public String getAppFuction() { return appFuction; } public void setAppFuction(String appFuction) { this.appFuction = appFuction; } public int getAppSystem() { return appSystem; } public void setAppSystem(int appSystem) { this.appSystem = appSystem; } } @Override public Product build() { product = new Product(); product.setAppName(innerProduct.getAppName()); product.setAppFuction(innerProduct.getAppFuction()); product.setAppSystem(innerProduct.getAppSystem()); return product; }}这里采用了一下建造者模式的变异,这样其实更有利于使用。至于为什么,待会再产品经理那使用就知道了。
/** * 产品经理 * Created by Administrator on 2016/12/29. */public class ProductManager { public static Product create(@AppSystem int system){ return new Progremer().setAppSystem(system).setAppName("探探").setAppFuction("划一划,找妹子。").build(); }}在这里,采用链式调用,非常方便。这也是Android在源码经常使用的一种模式。
在客户类里,只需要持有产品经理类之后,就可以得到产品了。
public class Client { public void main(String[] args){ //客户:我需要一个可以摇一摇找妹子的软件 //产品经理:分析得出那就做一个探探吧 //技术主管:appName:探探 系统:ios,android 功能:摇一摇,找妹子 Product android = ProductManager.create(Product.ANDROID); Product ios = ProductManager.create(Product.IOS); }}建造者模式也就这么多了。其实变异来的建造者可以只需要具体建造者,抽象的不要了。指挥者也可以不要了。
public class Client { public void main(String[] args){ //客户:我需要一个可以摇一摇找妹子的软件 //产品经理:分析得出那就做一个探探吧 //技术主管:appName:探探 系统:ios,android 功能:摇一摇,找妹子 Product android = ProductManager.create(Product.ANDROID); Product ios = ProductManager.create(Product.IOS); //程序猿觉得太累了,工资又少,干的最多。最后决定自己出去单干。 Progremer niubiProgremer = new Progremer(); Product androidBest = niubiProgremer.setAppName("探探").setAppSystem(Product.ANDROID).setAppFuction("摇一摇,找妹子").build(); Product iosBest = niubiProgremer.setAppName("探探").setAppSystem(Product.IOS).setAppFuction("摇一摇,找妹子").build(); }}到这里,有没有觉得眼熟啊,AlertDialog调用也是这样一大串,链式调度。非常方便。那么我开始撸AlertDialog的源码吧。
Android源码这里我给个百度云的连接,大家需要的自行下载。链接: 密码:5mfz
只用AlertDialog的时候,都知道。
AlertDialog.Builder builder = new AlertDialog.Builder(this);一看就知道创建AlertDialog时,需要通过内部类Builder来创建。那么我们来看一下Builder里有什么呢。AlertDailog就是建造者模式中指挥者的角色。
public static class Builder { private final AlertController.AlertParams P; private final int mTheme; public Builder(@NonNull Context context) { this(context, resolveDialogTheme(context, 0)); } public Builder(@NonNull Context context, @StyleRes int themeResId) { P = new AlertController.AlertParams(new ContextThemeWrapper( context, resolveDialogTheme(context, themeResId))); mTheme = themeResId; } @NonNull public Context getContext() { return P.mContext; } public Builder setTitle(@StringRes int titleId) { P.mTitle = P.mContext.getText(titleId); return this; } public Builder setTitle(CharSequence title) { P.mTitle = title; return this; } ........ public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) { P.mPositiveButtonText = P.mContext.getText(textId); P.mPositiveButtonListener = listener; return this; } public AlertDialog create() { final AlertDialog dialog = new AlertDialog(P.mContext, mTheme); P.apply(dialog.mAlert);//这里调用了apply真正创建了需要显示的dialog,也就是说之前的设置都是以P做一个数据缓存 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() { final AlertDialog dialog = create(); dialog.show(); return dialog; } }我把源码整理了一下,觉得有用的展示出来。在Builder创建了一个AlertController.AlertParams P;P是后面所有设置方法所调用的对象。那么我们再看看AlertController.AlertParams这个内部类有什么参数。
public static class AlertParams { public final Context mContext; public final LayoutInflater mInflater; public int mIconId = 0; public Drawable mIcon; public int mIconAttrId = 0; public CharSequence mTitle; public View mCustomTitleView; public CharSequence mMessage; public CharSequence mPositiveButtonText; ........ public AlertParams(Context context) { mContext = context; mCancelable = true; mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void apply(AlertController dialog) {//传入一个dialog,获取AlertParams缓存的数据。 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); } // 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); } } else if (mViewLayoutResId != 0) { dialog.setView(mViewLayoutResId); } /* dialog.setCancelable(mCancelable); dialog.setOnCancelListener(mOnCancelListener); if (mOnKeyListener != null) { dialog.setOnKeyListener(mOnKeyListener); } */ } ...... }
这里最主要的方法时apply(),在这里AlertParams 的所有属性转给了一个AlertController dialog。那么我们看看AlertController 是什么。
class AlertController { private final Context mContext; final AppCompatDialog mDialog; private final Window mWindow; private CharSequence mTitle; private CharSequence mMessage; ListView mListView; private View mView; private int mViewLayoutResId; private int mViewSpacingLeft; private int mViewSpacingTop; private int mViewSpacingRight; private int mViewSpacingBottom; private boolean mViewSpacingSpecified = false; Button mButtonPositive; private CharSequence mButtonPositiveText; Message mButtonPositiveMessage; Button mButtonNegative; private CharSequence mButtonNegativeText; Message mButtonNegativeMessage; Button mButtonNeutral; private CharSequence mButtonNeutralText; Message mButtonNeutralMessage; NestedScrollView mScrollView; private int mIconId = 0; private Drawable mIcon; private ImageView mIconView; private TextView mTitleView; private TextView mMessageView; private View mCustomTitleView; ListAdapter mAdapter; int mCheckedItem = -1; private int mAlertDialogLayout; private int mButtonPanelSideLayout; int mListLayout; int mMultiChoiceItemLayout; int mSingleChoiceItemLayout; int mListItemLayout; private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE; Handler mHandler; .......... }AlertController.AlertParams 持有AlertController的所有属性,在调用builder里的设置属性方法时,就是给AlertController.AlertParams做一个缓存。在调用了builder 的show方法之后。里面在调用具体dialog的show方法显示弹窗。
那么AlertDialog在建造者模式中担任的是指挥者,Bilder就是具体的建造者。采用了链式调用。AlertController是产品,而AlertController.AlertParams是产品的缓存。比如我调用了两次setTitle(),在缓存时后一次会覆盖前一次,这样就解决了开发者冲动调用的问题。最后不论是调用Builder的show方法,还是调用调用AlertDialog的show方法。都是允许的。
比如:
new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").show();
这样是可以的,最后调用的是Builder的show方法,在Builder的show方法中,调用了AlertDialog的create()得到缓存数据的AlertDialog进行显示。
new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").create().show();
这样也是可以的,手动调用create()方法,之后在调用AlertDialog的show方法进行显示。
那么这样呢?
new AlertDialog.Builder(this).setTitle("标题").setIcon(R.mipmap.ic_launcher).setMessage("测试").show().show();
第一个show是builder的show,里面创建了AlertDialog并且调用了AlertDialog的show方法。那么第二个show也是AlertDialog的show方法,会重复调用么?答案肯定是不可能的。看看Dialog的show方法源码、
public void show() { if (mShowing) { if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } ..... try { mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); } finally { } }
在调用底层的show方法时,会先进行一次判断,第一次show之后mShowing已经设为true。那么第二次调用时,判断到已经显示,就不会再次调用绘制逻辑(省略号部分)。
那么建造者模式就到这儿了,源码的博大精深真是令人向往。