(本文僅代表個人觀點,如果不對寡具,請留言指正)
最近在看新的網(wǎng)絡(luò)請求框架retrofit牍汹,看到他支持鏈?zhǔn)秸{(diào)用铐维,也就是我們知道的構(gòu)建者模式柬泽。好像很多開源的庫都熱衷于構(gòu)建者模式,究竟是跟風(fēng)還是確實有什么獨特的好處呢方椎?
我們通過一個案例體會一下他的好處聂抢。
案例一、提示彈框
開發(fā)過程中棠众,我們經(jīng)常需要彈出一個提示框:
這個彈框有標(biāo)題琳疏、提示內(nèi)容、取消按鈕闸拿、確認(rèn)按鈕等空盼,如果不使用構(gòu)建者模式,我之前的封裝是這樣的:
public class NoticeDialogUtils {
/**
* 只響應(yīng)一個按鈕的dialog
* @param context
* @param msg
*/
public static NoticeDialog notice0(Context context , String msg ){
return notice(context, "提醒", msg, null, "確定", true, null, new NoticeDialog.OnNoticeClickListener() {
@Override
public void onNoticeClick(NoticeDialog dialog, int which) {
dialog.dismiss();
}
});
}
/**
* 只響應(yīng)一個按鈕的dialog
* @param context
* @param msg
* @param listener
*/
public static NoticeDialog notice1(Context context , String msg , NoticeDialog.OnNoticeClickListener listener){
return notice(context, "提醒", msg, "取消", "確定", true, new NoticeDialog.OnNoticeClickListener() {
@Override
public void onNoticeClick(NoticeDialog dialog, int which) {
dialog.dismiss();
}
}, listener);
}
/**
* 只響應(yīng)一個按鈕的dialog 點擊外部不可以取消
* @param context
* @param msg
* @param listener
*/
public static NoticeDialog notice1Cancel(Context context , String msg , boolean cancel, NoticeDialog.OnNoticeClickListener listener){
return notice(context, "提醒", msg, "取消", "確定", cancel, new NoticeDialog.OnNoticeClickListener() {
@Override
public void onNoticeClick(NoticeDialog dialog, int which) {
dialog.dismiss();
}
}, listener);
}
/**
* 響應(yīng)兩個按鈕的dialog , 取消按鈕除了dismiss()會執(zhí)行操作
* @param context
* @param msg
* @param listener
*/
public static NoticeDialog notice2(Context context , String msg , NoticeDialog.OnNoticeClickListener listener){
return notice(context, "提醒", msg, "取消", "確定", listener);
}
/**
* 提示的dialog
* @param context
* @param title
* @param msg
* @param cancel
* @param confirm
* @param listener
*/
public static NoticeDialog notice(Context context , String title , String msg , String cancel , String confirm , NoticeDialog.OnNoticeClickListener listener){
return notice(context, title, msg, cancel, confirm, true, listener , listener);
}
/**
* 提示的dialog 可以取消
* @param context
* @param title
* @param msg
* @param cancel
* @param confirm
* @param listener
*/
public static NoticeDialog noticeCancel(Context context , String title , String msg , String cancel , String confirm , boolean cancelble, NoticeDialog.OnNoticeClickListener listener){
return notice(context, title, msg, cancel, confirm, cancelble, listener , listener);
}
/**
* 提示dialog
* @param context
* @param title
* @param msg
* @param cancel
* @param confirm
* @param cancelListener
* @param confirmListener
*/
private static NoticeDialog notice(Context context , String title , String msg , String cancel , String confirm ,boolean cancelble,
NoticeDialog.OnNoticeClickListener cancelListener , NoticeDialog.OnNoticeClickListener confirmListener){
NoticeDialog.Builder builder = new NoticeDialog.Builder(context);
NoticeDialog dialog = builder.setTitle(title)
.setOutSideCancelble(cancelble)
.setMessage(msg)
.setPositiveButton(confirm, confirmListener)
.setNegativeButton(cancel, cancelListener)
.create();
dialog.show();
return dialog;
}
}
然后調(diào)用的地方補(bǔ)充參數(shù):
NoticeDialogUtils.notice(mContext, "提醒", "返回添加賽事新荤,將放棄購買此保存訂單", "取消", "確定", new NoticeDialog.OnNoticeClickListener() {
@Override
public void onNoticeClick(NoticeDialog dialog, int which) {
switch (which) {
case NOTICE_CONFIRM:
dialog.dismiss();
onAddMatchClick();
break;
case NOTICE_CANCEL:
dialog.dismiss();
break;
}
}
});
代碼很簡潔揽趾,只是傳參的時候需要仔細(xì)對照api,可讀性不太友好苛骨。
實際上篱瞎,這樣寫已經(jīng)算合格了,沒有在需要彈框的時候去復(fù)制粘貼上一個彈框的代碼痒芝。那么還能不能優(yōu)化呢俐筋?我們看看利用構(gòu)建者模式封裝會是什么情況:
public class DialogUtils {
public static DialogUtils instance;
public static DialogUtils getInstance() {
if (instance == null) {
synchronized (DialogUtils.class) {
if (instance == null) {
instance = new DialogUtils();
}
}
}
return instance;
}
public static class Builder {
private Context context;
private View layout;
private int width;
private int gravity;
private int anim;
private String title = "提示";
private String notice = "";
private String confirm = "確定";
private String cancel = "取消";
private boolean outsideCancel = false;
private View.OnClickListener onClickListener;
public Builder context(Context context) {
this.context = context;
return this;
}
public Builder layout(View layout) {
this.layout = layout;
return this;
}
public Builder width(int width) {
this.width = width;
return this;
}
public Builder gravity(int gravity) {
this.gravity = gravity;
return this;
}
public Builder anim(int anim) {
this.anim = anim;
return this;
}
public Builder outsideCancel(boolean outsideCancel) {
this.outsideCancel = outsideCancel;
return this;
}
public Builder onClickListener(View.OnClickListener onClickListener) {
this.onClickListener = onClickListener;
return this;
}
public Builder title(String title) {
this.title = title;
return this;
}
public Builder notice(String notice) {
this.notice = notice;
return this;
}
public Builder confirm(String confirm) {
this.confirm = confirm;
return this;
}
public Builder cancel(String cancel) {
this.cancel = cancel;
return this;
}
public DialogUtils build() {
DialogUtils dialogUtils = DialogUtils.getInstance();
if (context == null) {
throw new IllegalArgumentException("context must be not null");
}
dialogUtils.context = this.context;
if (layout == null) {
LayoutInflater inflater = LayoutInflater.from(context);
this.layout = inflater.inflate(R.layout.dialog_common_dialog_layout, null);
dialogUtils.isUseDefaultLayout = true;
}
dialogUtils.layout = this.layout;
if (width == 0) {
width = (int) (context.getResources().getDisplayMetrics().widthPixels * 0.8);
}
dialogUtils.width = this.width;
if (gravity == 0) {
gravity = Gravity.CENTER;
}
dialogUtils.gravity = this.gravity;
if(anim == 0){
anim = R.style.dialog_default_anim;
}
dialogUtils.anim = this.anim;
dialogUtils.title = this.title;
dialogUtils.notice = this.notice;
dialogUtils.confirm = this.confirm;
dialogUtils.cancel = this.cancel;
dialogUtils.outsideCancel = this.outsideCancel;
dialogUtils.onClickListener = this.onClickListener;
return dialogUtils;
}
}
private Context context;
private View layout;
private int width;
private int gravity;
private int anim;
private String title;
private String notice;
private String confirm;
private String cancel;
private boolean outsideCancel;
private View.OnClickListener onClickListener;
//是否使用默認(rèn)的布局
private boolean isUseDefaultLayout;
public Dialog show() {
Dialog dialog = new Dialog(context, R.style.loading_dialog);
dialog.setContentView(layout);
dialog.setCanceledOnTouchOutside(outsideCancel);
Window window = dialog.getWindow();
if (window != null) {
window.getAttributes().width = width;
window.setGravity(gravity);
window.setWindowAnimations(anim);
}
if (isUseDefaultLayout) {
TextView tvTitle = layout.findViewById(R.id.tv_title);
TextView tvNotice = layout.findViewById(R.id.tv_notice);
TextView tvCancel = layout.findViewById(R.id.tv_cancel);
TextView tvConfirm = layout.findViewById(R.id.tv_confirm);
tvTitle.setText(title);
tvNotice.setText(notice);
tvCancel.setText(cancel);
tvConfirm.setText(confirm);
tvConfirm.setOnClickListener(onClickListener);
tvCancel.setOnClickListener(onClickListener);
}
dialog.show();
return dialog;
}
}
然后是調(diào)用的時候:
new DialogUtils.Builder()
.context(MainActivity.this)
.title("更新")
.notice("是否更新到最新版本?")
.onClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
})
.build()
.show();
直觀上严衬,可讀性非常強(qiáng)澄者,api很靈活,參數(shù)之間可以隨機(jī)組合请琳。
疑問
通過案例我們可以得出一個結(jié)論粱挡,構(gòu)建者的api很優(yōu)雅,但是build()這個方法是不是必要的呢俄精?
我們也可以直接在外部內(nèi)return this;為什么一定要增加一個內(nèi)部類呢询筏?
如下的寫法:
public class Out {
private String param;
public Out param(String param){
this.param = param;
return this;
}
public static class Inner{
private String param;
public Inner param(String param){
this.param = param;
return this;
}
public Out build(){
Out out = new Out();
out.param = param;
return out;
}
}
public void show(){
}
}
new Out.Inner().param("123").build();
new Out().param("123");
我們上下兩種方式都是可以傳入?yún)?shù)的。那么這個build()方法是不是很多余呢竖慧?
自然是不多余屈留。下面的寫法在任何一次調(diào)用之后都是能拿到Out這個對象進(jìn)行最終的方法show()的調(diào)用,但這是很危險的测蘑,因為參數(shù)還沒有確定完全準(zhǔn)備好,很可能發(fā)生空指針異常康二。
所以build()這個方法就可以進(jìn)行風(fēng)險規(guī)避碳胳,在每一個參數(shù)引用之前進(jìn)行判斷攔截:
public DialogUtils build() {
DialogUtils dialogUtils = DialogUtils.getInstance();
if (context == null) {
throw new IllegalArgumentException("context must be not null");
}
dialogUtils.context = this.context;
if (layout == null) {
LayoutInflater inflater = LayoutInflater.from(context);
this.layout = inflater.inflate(R.layout.dialog_common_dialog_layout, null);
dialogUtils.isUseDefaultLayout = true;
}
dialogUtils.layout = this.layout;
if (width == 0) {
width = (int) (context.getResources().getDisplayMetrics().widthPixels * 0.8);
}
dialogUtils.width = this.width;
if (gravity == 0) {
gravity = Gravity.CENTER;
}
dialogUtils.gravity = this.gravity;
if(anim == 0){
anim = R.style.dialog_default_anim;
}
dialogUtils.anim = this.anim;
dialogUtils.title = this.title;
dialogUtils.notice = this.notice;
dialogUtils.confirm = this.confirm;
dialogUtils.cancel = this.cancel;
dialogUtils.outsideCancel = this.outsideCancel;
dialogUtils.onClickListener = this.onClickListener;
return dialogUtils;
}
這就保證了代碼的健壯性。
案例二沫勿、網(wǎng)絡(luò)請求庫的封裝
正如上面提到的構(gòu)建者的好處挨约,所以我相信retrofit使用構(gòu)建者肯定是考慮到了這一點味混,因為網(wǎng)絡(luò)請求的時候參數(shù)的變化實在是太多了,如果使用傳統(tǒng)的方法傳參诫惭,將是一場災(zāi)難翁锡。所以趕緊改造自己的網(wǎng)絡(luò)請求庫吧。
代碼比較少夕土,不傳github了馆衔。