Android源码设计模式探索与实战【建造者模式】
IT行業,一直講一句話,拼到最后都拼的是“內功”,而內功往往就是指我們處理問題的思路、經驗、想法,而對于開發者來說,甚至對于產品也一樣,都離不開一個“寶典”,就是設計模式。今天我們一起借助Android源碼去探索一下建造者模式的優缺點,以及它所想要去解決的問題。同時結合我工作經驗中的一個小例子,來總結實踐一下。
1.背景&定義
理解:
建造者模式是創建性設計模式的一種。是我們最常見、也可能是開發者肯定會使用的一種設計模式。
先從建造者這個詞來理解,應該是用于建造一個東西而存在的設計模式。現實生活中對應的人或物或者事情,在代碼的世界中,都可以通過行為和屬性抽象為一個對象,而往往對象越復雜,new一個對象時,我們需要不斷的set去創建、而且,復雜對象的組裝過程,也是需要特定的順序,這時,為了解耦構建過程和組裝過程,建造者模式應運而生。
**我的理解:**該模式是為了將復雜對象的構建過程和組裝過程相分離,對外不可見。我們知道設計模式離不開一個詞解耦,建造者模式,為了解耦 構建過程和組裝過程,使構建過程可動態擴展,對組裝過程進行封裝
定義:
將一個復雜對象的構建與表示相分離,使得同樣的構建過程可以創建不同的表示。
2.UML類圖設計
3.源碼中的建造者模式
在Android源碼中,最常使用的Builder模式就是AlertDialog.Builder。使用該Builder創建不同的復雜的AlertDialog對象。我們接下來分析一下AlertDialog源碼。I看一下android源碼如何實現的?是否和我們上面想的UML一樣。
首先看到AlertDialog內部有一個內部類,Builder類,不出所料的話,這應該是一個靜態內部類(為什么是靜態內部類?記得【Android進階】篇章里面我們說到的內部類引起的內存泄露了嗎?如果忘記了,快去復習一下吧!!!)
AlertDialog.Builder
從這里源碼分析,的確是使用了builder方式,但是AlertDialog這里使用了一個AlertParams,從字面意思理解,是Dialog的參數的一個類,我們點進去看一下,果不其然,AlertParams是封裝所有Dialog屬性參數的一個類而已。
AlertController.AlertParams.java
從這里已經能看到,這里包含了很多屬性參數,這也是AlertDialog源碼比較巧妙的一點,以后大家自己實現復雜對象的Builder模式時,我們也可以不要把所有的代碼寫在一個類中,可以把這部分參數屬性分離(單一原則 & 其他模塊也可復用)。
好了,我們接著看,AlertDialog.Builder里面的具體方法
public static class Builder {public Builder(Context context, int themeResId) {P = new AlertController.AlertParams(new ContextThemeWrapper(context, resolveDialogTheme(context, themeResId)));}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 setCustomTitle(View customTitleView) {P.mCustomTitleView = customTitleView;return this;}public Builder setMessage(@StringRes int messageId) {P.mMessage = P.mContext.getText(messageId);return this;}//構建過程中,一直填充的是AlertParams屬性public Builder setMessage(CharSequence message) {P.mMessage = message;return this;}//真正調用create時,才去創建了AlertDialog 對象,并且把AlertParams的參數,一一對應賦值給了AlertDialog public AlertDialog create() {// Context has already been wrapped with the appropriate theme.final AlertDialog dialog = new AlertDialog(P.mContext, 0, 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;}我們看一下 P.apply(dialog.mAlert);
``AlertParams.apply
可以看到這里,的確是把AlertParams的參數,一一對應賦值給了AlertDialog 。看到這里,這時完成了AlertDialog的創建過程。但是最重要的show還沒有看,我們繼續扒一扒。
知識點引申擴展:Dialog show的源碼分析
public void show() {//當前是否正在顯示if (mShowing) {//當前view不為null,并且有菜單標題欄,則去創建并顯示if (mDecor != null) {if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);}mDecor.setVisibility(View.VISIBLE);}//如果正在顯示中,則returnreturn;}mCanceled = false;if (!mCreated) {//如果沒有創建完成,代碼view的contentview還未創建完成,則執行view的創建過程,就是onCreate方法dispatchOnCreate(null);} else {// Fill the DecorView in on any configuration changes that// may have occured while it was removed from the WindowManager.final Configuration config = mContext.getResources().getConfiguration();mWindow.getDecorView().dispatchConfigurationChanged(config);}//執行Dialog的onStart方法onStart();//獲取當前的視圖mDecor = mWindow.getDecorView();if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {final ApplicationInfo info = mContext.getApplicationInfo();mWindow.setDefaultIcon(info.icon);mWindow.setDefaultLogo(info.logo);mActionBar = new WindowDecorActionBar(this);}WindowManager.LayoutParams l = mWindow.getAttributes();boolean restoreSoftInputMode = false;if ((l.softInputMode& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {l.softInputMode |=WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;restoreSoftInputMode = true;}//將當前視圖按照布局參數,添加到當前activity所處window的視圖中mWindowManager.addView(mDecor, l);if (restoreSoftInputMode) {l.softInputMode &=~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;}mShowing = true;sendShowMessage();}Dialog.show函數關鍵做了以下三步:
1)dispatchOnCreate,調用Dialog的onCreate,創建視圖view
2)執行Dialog的onStart方法
3)將當前視圖按照布局參數,添加到當前dialog所處window的視圖中
Dialog.dispatchOnCreate
很明顯,其實Dialog的創建,也是一系列自定義生命周期函數的調用過程,我們接下來看一下AlertDialog的onCreate
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//mAlert在上面參數構建分析的時候,我們知道是一個封裝類AlertControllermAlert.installContent();}AlertController.installContent
AlertController#installContent
public void installContent() {//選擇指定的視圖布局final int contentView = selectContentView();//window設置內容視圖布局mDialog.setContentView(contentView);//初始化視圖內容setupView();}默認視圖結構
Dialog.setContentView
這里獲取到layoutID之后,調用了Dialog.setContentView方法
/*** Set the screen content from a layout resource. The resource will be* inflated, adding all top-level views to the screen.* * @param layoutResID Resource ID to be inflated.*/public void setContentView(@LayoutRes int layoutResID) {//這里的window是個啥,我們在Dialog的源碼中找一找mWindow.setContentView(layoutResID);}我們跟蹤源碼,可以看到是在Dialog的構造函數里面創建的
Dialog#構造函數·
小結
調用AlertDialog的show函數之后,其實就是調用了AlertDialog的一系列生命函數,完成PhoneWindow的創建、視圖的創建、視圖的內容設置,然后通過WiindowManager,將創建的view add進去,最終用戶就可以看到這個Dialog。
–引入一個小的課后作用知識點,大家通過上面代碼,知道了DIalog其實是創建了一個新的PhoneWindow,我們之前【Android進階】中講到過,activity實際上在Acitviy中,也會創建PhoneWindow對象, 這兩者有什么區別嗎?
4.使用場景&優缺點總結
Builder模式在Android開發中較為常用,通常作為配置類的構建器將配置的構建和表示分離開來,同時也是將配置從目標類中隔離出來,避免過多的setter方法。Builder模式比較常見的實現形式是通過調用鏈實現,這樣的代碼更簡潔、易懂。
4.1 使用場景
(1)相同的方法,不同的執行順序,產生不同的事件結果時,可以采用建造者模式
(2)多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不相同時,則可以使用該模式。
(3)產品類非常復雜,或者產品類中的調用順序不同產生了不同的效能,這個時候使用建造者模式非常合適
(4)在對象創建過程中會使用到系統中的一些其他對象,這些對象在產品對象的創建過程中不易得到時,也可以采用建造者模式封裝該對象的創建過程
4.2 優點
(1)良好的封裝性,使用構建者模式可以使客戶端不必知道內部的組成細節。
(2)建造者獨立,容易擴展。
4.3 缺點
會產生多于的Builder對象以及Director對象,消耗內存。
5.實踐經驗總結
5.1 自定義NavigationBar的設計
android開發中,頂部導航欄是常用的一個控件,如下
其實導航欄無非以下幾個步驟:
1)加載導航欄布局文件
2)構建視圖元素
3)設置視圖元素的文本、事件
4)添加到父視圖,最后展示
5)這里不妨,我們增加一條,寫一個固定的view很簡單,如果支持擴展,需要考慮一下
使用一下
private void testBuilderPatterm() {new NavigationBar.Builder(MainActivity.this, R.layout.navigation_layout, (ViewGroup) getWindow().getDecorView()).setBackColor(com.google.android.material.R.color.design_default_color_on_secondary).setTextToButtonView(R.id.back_button, "返回").setTextToTextView(R.id.title_textview, "我是標題").setOnClickListenerToButtonView(R.id.back_button, new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}}).show();}5.2 通用Dialog框架設計
業務開發中,經常會開發各種各樣的彈窗樣式,例如:倒計時彈窗、進度條彈窗、等待彈窗、通知彈窗、兩個button的彈窗、單個button的彈窗等等,在一個app或者一個系統中,往往彈窗風格肯定是統一的,所以大家為了方便使用,一般都會封裝各種框架,這里小編一起和大家封裝一個Dialog框架,滿足以下需求
1)彈窗可以根據是否設置了哪些信息,去自動選擇不同的dialog布局,例如:如果只設置了一個button的文本和事件,那么選擇只有一個button的layout;如果設置了消息,則有通知區域,反之沒有通知區域;
2)彈窗框架,需要做一些異常規避,而不是導致調用者的app崩潰,例如用戶調用沒有設置title,那么拋出相應異常或者錯誤給到調用者;例如沒有找到相應layoutID,則通知調用者,而不是應用崩潰;
3)彈窗框架,需要包含多種樣式,例如進度彈窗、倒計時彈窗
效果如下:
話不多說,我們直接上代碼。
5.2.1 公共彈窗控件封裝
package com.itbird.design.builder.dialog;import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.TextView;import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import androidx.annotation.StyleRes;import com.itbird.design.R;import java.lang.ref.WeakReference;/*** 公共彈窗框架封裝* Created by xfkang on 2020/5/23.*/public class CommonDialog extends Dialog implements DialogInterface {private static final int DIALOG_STYLE_SMALL = 1;private static final int DIALOG_STYLE_NORMAL = 2;private static final int DIALOG_STYLE_HIGH = 3;private ButtonHandler handler;private View rootView;private int dialogStyle;private TextView titleTextView;private TextView messageTextView;private Button positiveButton;private Button negativeButton;private Message positiveMessage;private Message negativeMessage;public CommonDialog(@NonNull Context context) {super(context);}public CommonDialog(@NonNull Context context, @StyleRes int themeResId) {super(context, themeResId);}protected CommonDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) {super(context, cancelable, cancelListener);}CommonDialog(Builder builder) {super(builder.context, R.style.common_dialog_style);rootView = LayoutInflater.from(builder.context).inflate(getInflateLayout(builder), null);setContentView(rootView);setupView();handler = new ButtonHandler(this);setWindowStyle();}private void setWindowStyle() {Window window = getWindow();WindowManager.LayoutParams layoutParams = window.getAttributes();int width = getContext().getResources().getDimensionPixelOffset(R.dimen.dialog_width);int height = 0;switch (dialogStyle) {case DIALOG_STYLE_SMALL:height = getContext().getResources().getDimensionPixelOffset(R.dimen.dialog_small_height);break;case DIALOG_STYLE_NORMAL:height = getContext().getResources().getDimensionPixelOffset(R.dimen.dialog_normal_height);break;default:height = getContext().getResources().getDimensionPixelOffset(R.dimen.dialog_small_height);break;}layoutParams.width = width;layoutParams.height = height;window.setAttributes(layoutParams);}private void setupView() {titleTextView = (TextView) findViewById(R.id.title);messageTextView = (TextView) findViewById(R.id.message);positiveButton = (Button) findViewById(R.id.positive_button);if (positiveButton != null) {positiveButton.setOnClickListener(mButtonHandler);}negativeButton = (Button) findViewById(R.id.negative_button);if (negativeButton != null) {negativeButton.setOnClickListener(mButtonHandler);}}public void setTitle(String title) {if (titleTextView != null) {titleTextView.setText(title);}}public void setMessage(String message) {if (messageTextView != null) {messageTextView.setText(message);}}public void setPositiveButton(String text, final OnClickListener onClickListener) {if (positiveButton != null) {positiveButton.setText(text);if (onClickListener != null) {positiveMessage = handler.obtainMessage(DialogInterface.BUTTON_POSITIVE, onClickListener);}}}public void setNegativeButton(String text, final OnClickListener onClickListener) {if (negativeButton != null) {negativeButton.setText(text);if (onClickListener != null) {negativeMessage = handler.obtainMessage(DialogInterface.BUTTON_NEGATIVE, onClickListener);}}}private int getInflateLayout(Builder builder) {if (TextUtils.isEmpty(builder.title)) {throw new IllegalStateException("No title for dialog");}int layoutResID = 0;if (!TextUtils.isEmpty(builder.message)&& TextUtils.isEmpty(builder.positiveText)&& TextUtils.isEmpty(builder.negativeText)) {layoutResID = R.layout.common_no_button_dialog;dialogStyle = 1;}if (TextUtils.isEmpty(builder.message)&& !TextUtils.isEmpty(builder.positiveText)&& TextUtils.isEmpty(builder.negativeText)) {layoutResID = R.layout.common_no_message_one_button_dialog;dialogStyle = 1;}if (TextUtils.isEmpty(builder.message)&& !TextUtils.isEmpty(builder.positiveText)&& !TextUtils.isEmpty(builder.negativeText)) {layoutResID = R.layout.common_no_message_two_button_dialog;dialogStyle = 1;}if (!TextUtils.isEmpty(builder.message)&& !TextUtils.isEmpty(builder.positiveText)&& !TextUtils.isEmpty(builder.negativeText)) {layoutResID = R.layout.common_message_two_button_dialog;dialogStyle = 2;}if (!TextUtils.isEmpty(builder.message)&& !TextUtils.isEmpty(builder.positiveText)&& TextUtils.isEmpty(builder.negativeText)) {layoutResID = R.layout.common_message_one_button_dialog;dialogStyle = 2;}if (layoutResID == 0) {throw new IllegalStateException("Not have this dialog");}return layoutResID;}private final View.OnClickListener mButtonHandler = new View.OnClickListener() {@Overridepublic void onClick(View v) {final Message m;if (v == positiveButton && positiveMessage != null) {m = Message.obtain(positiveMessage);} else if (v == negativeButton && negativeMessage != null) {m = Message.obtain(negativeMessage);} else {m = null;}if (m != null) {m.sendToTarget();}handler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, CommonDialog.this).sendToTarget();}};public static class Builder {private String title;private String message;private String positiveText;private OnClickListener positiveOnClickListener;private String negativeText;private OnClickListener negativeOnCLickListener;private boolean cancelable = true;private OnCancelListener onCancelListener;private OnDismissListener onDismissListener;private OnKeyListener onKeyListener;private final Context context;public Builder(Context context) {this.context = context;}public Builder setTitle(@StringRes int resID) {this.title = context.getResources().getString(resID);return this;}public Builder setTitle(String title) {this.title = title;return this;}public Builder setMessage(@StringRes int resID) {this.message = context.getResources().getString(resID);return this;}public Builder setMessage(String message) {this.message = message;return this;}public Builder setPositiveButton(@StringRes int resID, OnClickListener onClickListener) {this.positiveText = context.getResources().getString(resID);this.positiveOnClickListener = onClickListener;return this;}public Builder setPositiveButton(String text, OnClickListener onClickListener) {this.positiveText = text;this.positiveOnClickListener = onClickListener;return this;}public Builder setNegativeButton(@StringRes int resID, OnClickListener onClickListener) {this.negativeText = context.getResources().getString(resID);this.negativeOnCLickListener = onClickListener;return this;}public Builder setNegativeButton(String text, OnClickListener onClickListener) {this.negativeText = text;this.negativeOnCLickListener = onClickListener;return this;}public Builder setCancelable(boolean cancelable) {this.cancelable = cancelable;return this;}public Builder setOnCancelListener(OnCancelListener onCancelListener) {this.onCancelListener = onCancelListener;return this;}public Builder setOnDismissListener(OnDismissListener onDismissListener) {this.onDismissListener = onDismissListener;return this;}public Builder setOnKeyListener(OnKeyListener onKeyListener) {this.onKeyListener = onKeyListener;return this;}public CommonDialog create() {CommonDialog commonDialog = new CommonDialog(this);apply(commonDialog);commonDialog.setCancelable(cancelable);if (cancelable) {commonDialog.setCanceledOnTouchOutside(true);}commonDialog.setOnCancelListener(onCancelListener);commonDialog.setOnDismissListener(onDismissListener);if (onKeyListener != null) {commonDialog.setOnKeyListener(onKeyListener);}return commonDialog;}public CommonDialog show() {CommonDialog commonDialog = create();commonDialog.show();return commonDialog;}private void apply(CommonDialog commonDialog) {commonDialog.setTitle(title);commonDialog.setMessage(message);commonDialog.setPositiveButton(positiveText, positiveOnClickListener);commonDialog.setNegativeButton(negativeText, negativeOnCLickListener);}}/*** 使用handler進行事件轉發* 為了防止內存泄露,使用弱引用*/private static final class ButtonHandler extends Handler {private static final int MSG_DISMISS_DIALOG = 1;private WeakReference<DialogInterface> mDialog;public ButtonHandler(DialogInterface dialog) {mDialog = new WeakReference<>(dialog);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case DialogInterface.BUTTON_POSITIVE:case DialogInterface.BUTTON_NEGATIVE:case DialogInterface.BUTTON_NEUTRAL:((OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);break;case MSG_DISMISS_DIALOG:((DialogInterface) msg.obj).dismiss();}}} }5.2.2 倒計時控件封裝
倒計時實現方式有很多種,例如Rxjava、TimerTask等,但是我們是為了去封裝一個控件,所以肯定不會去在框架中引用各種第三方框架的,應該去研究他們內部怎么去實現。不過我想應該逃脫不了handler、thread這些關鍵詞吧。
實現方式
1)基于android.os.CountDownTimer的源碼來設計實現,我們知道android原生這個控件是通過handler.postDelay來實現的,而且里面有進度回調,但是沒有暫停和恢復,所以我們需要添加onPause、onRestart自定義方法,
2)基于 android.widget.TextClock的源碼來設計實現,們知道android原生這個控件是通過handler.postAtTime + thread來實現的,我們之前是通過postDelay來觸發消息事件的,但這里系統使用了postAtTime,這樣就是設置了在某一個時間點拋出handler消息,前面的
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
精確控制了1秒的整點時間,因此系統可以在每一個整秒的時間點發出消息。
CustomCountDownTimer.java
package com.itbird.design.builder.dialog;import android.os.Handler; import android.os.Message; import android.os.SystemClock;/*** 使用android.os.CountDownTimer的源碼* 添加了onPause、onRestart自定義方法* Created by xfkang on 16/3/18.*/public abstract class CustomCountDownTimer {private static final int MSG = 1;/*** 總倒計時時間* Millis since epoch when alarm should stop.*/private final long mMillisInFuture;/*** 倒計時間隔時間* The interval in millis that the user receives callbacks*/private final long mCountdownInterval;/*** 記錄開始之后,應該停止的時間節點*/private long mStopTimeInFuture;/*** 記錄暫停的時間節點*/private long mPauseTimeInFuture;/*** 對應于源碼中的cancle,即計時停止時* boolean representing if the timer was cancelled*/private boolean isStop = false;private boolean isPause = false;/*** @param millisInFuture 總倒計時時間* @param countDownInterval 倒計時間隔時間*/public CustomCountDownTimer(long millisInFuture, long countDownInterval) {// 解決秒數有時會一開始就減去了2秒問題(如10秒總數的,剛開始就8999,然后沒有不會顯示9秒,直接到8秒)if (countDownInterval > 1000) {millisInFuture += 15;}mMillisInFuture = millisInFuture;mCountdownInterval = countDownInterval;}private synchronized CustomCountDownTimer start(long millisInFuture) {isStop = false;if (millisInFuture <= 0) {onFinish();return this;}mStopTimeInFuture = SystemClock.elapsedRealtime() + millisInFuture;mHandler.sendMessage(mHandler.obtainMessage(MSG));return this;}/*** 開始倒計時*/public synchronized final void start() {start(mMillisInFuture);}/*** 停止倒計時*/public synchronized final void stop() {isStop = true;mHandler.removeMessages(MSG);}/*** 暫時倒計時* 調用{@link #restart()}方法重新開始*/public synchronized final void pause() {if (isStop) return;isPause = true;mPauseTimeInFuture = mStopTimeInFuture - SystemClock.elapsedRealtime();mHandler.removeMessages(MSG);}/*** 重新開始*/public synchronized final void restart() {if (isStop || !isPause) return;isPause = false;start(mPauseTimeInFuture);}/*** 倒計時間隔回調** @param millisUntilFinished 剩余毫秒數*/public abstract void onTick(long millisUntilFinished);/*** 倒計時結束回調*/public abstract void onFinish();private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {synchronized (CustomCountDownTimer.this) {if (isStop || isPause) {return;}final long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();if (millisLeft <= 0) {onFinish();} else if (millisLeft < mCountdownInterval) {// no tick, just delay until donesendMessageDelayed(obtainMessage(MSG), millisLeft);} else {long lastTickStart = SystemClock.elapsedRealtime();onTick(millisLeft);// take into account user's onTick taking time to executelong delay = lastTickStart + mCountdownInterval - SystemClock.elapsedRealtime();// special case: user's onTick took more than interval to// complete, skip to next intervalwhile (delay < 0) delay += mCountdownInterval;sendMessageDelayed(obtainMessage(MSG), delay);}}}}; }小結
不管使用哪種方式實現倒計時,一般繞不過去handler和thread,使用這兩者需要解決兩個問題,一個是handler持有引用導致內存泄露問題,一個是handler postDealy會有消息處理第一次的跳變問題(如果使用handler.postDealyed(……, 1000)方式來進行每秒的計時,是不準確的,是的,有很大誤差,誤差的原因在于在你收到消息,到你重新發出handler.postDealyed的時間,并不是瞬間完成的,這里面有很多邏輯處理的時間,即使沒有邏輯處理的時間,handler本身也是耗損性能的,所以消息并不可能按照理想的1000延遲來進行發送,這就導致了誤差的累積)
1)內存泄露的問題解決方法:弱引用
2)跳變問題的解決方法:通過時間校準(實現方式也有多種,例如TextClock的postAttime(now+1000-now%1000),或者CountDownTimer去加一定的時間,但是這個不太好控制,不建議使用這種),來確保消息是在整數節點發出
6.第三方框架中的建造者模式(Glide、Retrofit)
Retrofit相信大家不陌生了,我們也知道里面依賴了okhttp,OkHttpClient,這個是整個OkHttp的核心管理類,內部包含了請求調度器(Dispatcher),請求攔截器(interceptors),代理,讀寫超時時間等各種需要配置的對象。
我們反過來結合之前所總結的建造者設計模式適用的場景,來理解一下。這里不就符合第一條嗎?
多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不相同時,則可以使用該模式。
這些配置的對象就是構建OkhttpClient的多個部件/零件。配置不同,導致的運行結果也不同。就像讀寫超時配置的超時時間不同,導致請求結果允許超時時間也不同。
接下來,我們看一下OkHttpClient中如何創建對象的。(由于okttp這個類代碼比較多,我們直接截圖說明重點)
我們看到OkHttpClient構造器里面傳入了Builder,OkHttpClient的所有屬性其實都依賴獲取于Builder,這個Builder是個啥?
其實就是OkHttpClient的一個內部類而已,這不正符合了使用場景中的3嗎?
(3)產品類非常復雜,或者產品類中的調用順序不同產生了不同的效能,這個時候使用建造者模式非常合適
還有一點,其實在這個過程中也用到了,不知道大家感覺到了沒有,就是第4點
(4)在對象創建過程中會使用到系統中的一些其他對象,這些對象在產品對象的創建過程中不易得到時,也可以采用建造者模式封裝該對象的創建過程
我們通過Builder去設置各種參數屬性時,對于使用者來說,只需要關注這些需要設置的屬性,而不需要關心,OkHttpClient內部用這些屬性,去組合構造了不同的對象。這就是上面說的這點了。其實這也是符合我們之前所講的,面向對象六大基本原則中的迪米特原則(最少知道原則)。
其實說白了,就是您封裝一個框架(通過各種設計模式),肯定要做到一點,對于調用者來說,不用關心內部細節,例如用哪些屬性組合什么對象,而且可以盡量規避各種由于調用者輸入異常、輸入丟失導致的框架異常等問題。
封裝一個框架需要做到的:
1)對于調用者來說,集成簡單、使用簡單,只需關注需要設置的參數即可
2)框架內部,對于異常參數輸入、內部執行異常,有回調、規避手段
3)最重要的一點,設計一個框架,不可能完美覆蓋所有的用戶需求,所以要通過接口和抽象,去支持擴展,而非去修改您的框架去達到目的
整體設計模式Demo代碼
總結
以上是生活随笔為你收集整理的Android源码设计模式探索与实战【建造者模式】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 抖音壁纸表情包小程序源码,可对接流量主
- 下一篇: PC 新时代即将到来,Windows 1