对于使用构建者模式的思考

(本文仅代表个人观点,如果不对,请留言指正)

最近在看新的网络请求框架retrofit,看到他支持链式调用,也就是我们知道的构建者模式。好像很多开源的库都热衷于构建者模式,究竟是跟风还是确实有什么独特的好处呢?

我们通过一个案例体会一下他的好处。

案例一、提示弹框

开发过程中,我们经常需要弹出一个提示框:

这个弹框有标题、提示内容、取消按钮、确认按钮等,如果不使用构建者模式,我之前的封装是这样的:

public class NoticeDialogUtils {

    /**
     * 只响应一个按钮的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();
            }
        });
    }

    /**
     * 只响应一个按钮的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);
    }


    /**
     * 只响应一个按钮的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);
    }

    /**
     * 响应两个按钮的dialog , 取消按钮除了dismiss()会执行操作
     * @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;
    }
}

然后调用的地方补充参数:

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;
                }
            }
        });

代码很简洁,只是传参的时候需要仔细对照api,可读性不太友好。

实际上,这样写已经算合格了,没有在需要弹框的时候去复制粘贴上一个弹框的代码。那么还能不能优化呢?我们看看利用构建者模式封装会是什么情况:

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;
    //是否使用默认的布局
    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;
    }
}

然后是调用的时候:

    new DialogUtils.Builder()
                .context(MainActivity.this)
                .title("更新")
                .notice("是否更新到最新版本?")
                .onClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                    }
                })
                .build()
                .show();

直观上,可读性非常强,api很灵活,参数之间可以随机组合。

疑问

通过案例我们可以得出一个结论,构建者的api很优雅,但是build()这个方法是不是必要的呢?
我们也可以直接在外部内return this;为什么一定要增加一个内部类呢?
如下的写法:

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");
    

我们上下两种方式都是可以传入参数的。那么这个build()方法是不是很多余呢?
自然是不多余。下面的写法在任何一次调用之后都是能拿到Out这个对象进行最终的方法show()的调用,但这是很危险的,因为参数还没有确定完全准备好,很可能发生空指针异常。
所以build()这个方法就可以进行风险规避,在每一个参数引用之前进行判断拦截:

    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;
        }

这就保证了代码的健壮性。

案例二、网络请求库的封装

正如上面提到的构建者的好处,所以我相信retrofit使用构建者肯定是考虑到了这一点,因为网络请求的时候参数的变化实在是太多了,如果使用传统的方法传参,将是一场灾难。所以赶紧改造自己的网络请求库吧。

代码比较少,不传github了。

你可能感兴趣的:(对于使用构建者模式的思考)