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

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

最近在看新的网络请求框架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了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,147评论 25 707
  • 如何安抚一颗向远方张望的心,唯有去远方。 于是,扶老携幼也来到了西安。这一大家子人啊,和西安这座城市留给我的印象一...
    好好做教育的暖暖阅读 224评论 0 0
  • 生活中,我们可以学到很多;学习中,我们可以学到很多;往事中,我们同样可以学到很多。也许,稚嫩的我们还不理解生活的困...
    YolandaYanyoyo阅读 201评论 0 0
  • 【今日打卡51】今天,我们单位的编辑找到我说我的文章有些内容写的太敏感了。就是那个党员同事前后对比的帮扶材料。作为...
    邓男神Sweety阅读 133评论 0 0