设计模式2-Builder建造者模式 提高易用性,减少使用困扰

一、模式定义

建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。建造者模式属于对象创建型模式。因为翻译的不同,建造者模式又可以称为生成器模式。

举个栗子,之前有讲过ImageLoader的栗子,在ImageLoader中,提供了setImageCache、setLoadingImage、setLoadingFailed、setThreadCount、setPlaceHolder等方法,这些参数是ImageLoader类的成员变量,通过setter方法,让调用者设置这些变量的值,使这些参数都能个被用户自由定制。

但是就会出现问题:1、用户可以再任意时间,修改ImageLoader的配置,如果已经初始化过了线程池数量的情况下,再次调用setThreadCount 会出现什么结果??什么结果不重要,重要的是,这样提高了用户的使用成本,里面暴露 函数过多,需要用户再使用的时候仔细选择,会给使用的人造成困扰

二、模式结构

建造者模式包含如下角色(这些都是老逻辑了,新的实现方法,Builder是一个链式调用,本次setter后都返回本身,Director可以被省略):

  • Builder:抽象建造者
  • ConcreteBuilder:具体建造者
  • Director:指挥者
  • Product:产品角色

[图片上传失败...(image-d3db8f-1554990544119)]

三、时序图

[图片上传失败...(image-6e86dd-1554990544119)]

四、简单实现

图片加载的过程,步骤也很多,需要配置url、占位图、缩放类型、动画、进度条 等等,顺序是不固定的,下面就举一个项目中正在使用的例子,解释经典的Builder模式
图片加载时,参数配置:

package cc.kaipao.dongjia.imageloader;

import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;

import cc.kaipao.dongjia.imageloader.base.LoadListener;

/**
 * 图片加载信息配置类
 *
 * Created by XiaoFeng on 2017/3/14.
 */

public class ImageConfig {

    private enum ScaleType {
        FIT_CENTER,
        CENTER_CROP,
        FIT_START
    }

    private String url;
    private @DrawableRes int placeholderResId;
    private @DrawableRes int failureResId;
    private ColorDrawable placeholderDrawable;
    private ColorDrawable failureDrawable;
    private ImageStyle style;
    private float roundRadius;
    private float borderWidth;
    private @ColorInt int borderColor;
    private boolean noFade;
    private int fadeDuration;
    private ScaleType scaleType;
    private boolean needProgress;
    private @DrawableRes int progressBgResId;
    private ColorDrawable progressBgDrawable;
    private LoadListener loadListener;
    private int width;
    private int height;

    private ImageConfig(Builder builder) {
        this.url = builder.url;
        this.placeholderResId = builder.placeholderResId;
        this.failureResId = builder.failureResId;
        this.placeholderDrawable = builder.placeholderDrawable;
        this.failureDrawable = builder.failureDrawable;
        this.style = builder.style;
        this.roundRadius = builder.roundRadius;
        this.borderWidth = builder.borderWidth;
        this.borderColor = builder.borderColor;
        this.noFade = builder.noFade;
        this.fadeDuration = builder.fadeDuration;
        this.scaleType = builder.scaleType;
        this.needProgress = builder.needProgress;
        this.progressBgResId = builder.progressBgResId;
        this.progressBgDrawable = builder.progressBgDrawable;
        this.loadListener = builder.loadListener;
        this.width = builder.width;
        this.height = builder.height;
    }
    public static Builder builder() {
        return new Builder();
    }

    public String getUrl() {
        return url;
    }

    public int getPlaceholderResId() {
        return placeholderResId;
    }

    public int getFailureResId() {
        return failureResId;
    }

    public ColorDrawable getPlaceholderDrawable() {
        return placeholderDrawable;
    }

    public ColorDrawable getFailureDrawable() {
        return failureDrawable;
    }

    public boolean isNeedProgress() {
        return needProgress;
    }

    public int getProgressBgResId() {
        return progressBgResId;
    }

    public ColorDrawable getProgressBgDrawable() {
        return progressBgDrawable;
    }

    public ImageStyle getStyle() {
        return style;
    }

    public float getRoundRadius() {
        return roundRadius;
    }

    public float getBorderWidth() {
        return borderWidth;
    }

    public int getBorderColor() {
        return borderColor;
    }

    public boolean isNoFade() {
        return noFade;
    }

    public boolean isFitCenter() {
        return scaleType == ScaleType.FIT_CENTER;
    }

    public boolean isCenterCrop() {
        return scaleType == ScaleType.CENTER_CROP;
    }

    public int getFadeDuration() {
        return fadeDuration;
    }

    public LoadListener getLoadListener() {
        return loadListener;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public static class Builder {
        // 图片url
        private String url;
        // 占位图
        private @DrawableRes int placeholderResId;
        // 失败图
        private @DrawableRes int failureResId;
        // 占位图
        private ColorDrawable placeholderDrawable;
        // 失败图
        private ColorDrawable failureDrawable;
        // 图片显示风格
        private ImageStyle style;
        // 圆角半径
        private float roundRadius;
        // 边框宽度
        private float borderWidth;
        // 边框颜色
        private @ColorInt int borderColor;
        // 是否是否淡入淡出
        private boolean noFade;
        // 淡入淡出时间间隔
        private int fadeDuration;
        // 缩放类型
        private ScaleType scaleType;
        // 是否显示进度条
        private boolean needProgress;
        // 进度条背景图
        private @DrawableRes int progressBgResId;
        // 进度条背景图
        private ColorDrawable progressBgDrawable;
        // 图片加载成功或失败的回调
        private LoadListener loadListener;

        /** 缩略图尺寸 */
        private int width;
        /** 缩略图尺寸 */
        private int height;

        public Builder() {
            this.style = ImageStyle.NORMAL;
            this.roundRadius = 0;
            this.borderWidth = 0;
            this.borderColor = Color.TRANSPARENT;
            this.noFade = false;
            this.fadeDuration = 0;
            this.scaleType = ScaleType.CENTER_CROP;
            this.needProgress = false;
            this.width =0;
            this.height = 0;
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder placeholderImage(@DrawableRes int placeholderResId) {
            this.placeholderResId = placeholderResId;
            return this;
        }

        public Builder failureImage(@DrawableRes int failureResId) {
            this.failureResId = failureResId;
            return this;
        }

        public Builder placeholderImage(ColorDrawable placeholderDrawable) {
            this.placeholderDrawable = placeholderDrawable;
            return this;
        }

        public Builder failureImage(ColorDrawable failureDrawable) {
            this.failureDrawable = failureDrawable;
            return this;
        }

        @Deprecated
        public Builder needProgress() {
            this.needProgress = true;
            return this;
        }

        public Builder progressBackground(@DrawableRes int progressBgResId) {
            this.progressBgResId = progressBgResId;
            return this;
        }

        public Builder progressBackground(ColorDrawable progressBgDrawable) {
            this.progressBgDrawable = progressBgDrawable;
            return this;
        }

        public Builder style(ImageStyle style) {
            this.style = style;
            return this;
        }

        public Builder roundRadius(float radius) {
            this.roundRadius = radius;
            return this;
        }

        public Builder borderWidth(float borderWidth) {
            this.borderWidth = borderWidth;
            return this;
        }

        public Builder borderColor(@ColorInt int borderColor) {
            this.borderColor = borderColor;
            return this;
        }

        public Builder noFade() {
            this.noFade = true;
            return this;
        }

        public Builder fade(int duration) {
            this.fadeDuration = duration;
            return this;
        }

        public Builder fitCenter() {
            this.scaleType = ScaleType.FIT_CENTER;
            return this;
        }
        public Builder fitStart() {
            this.scaleType = ScaleType.FIT_START;
            return this;
        }

        public Builder centerCrop() {
            this.scaleType = ScaleType.CENTER_CROP;
            return this;
        }

        public Builder width(int width) {
            this.width = width;
            return this;
        }


        public Builder height(int height) {
            this.height = height;
            return this;
        }

        public Builder loadListener(LoadListener loadListener) {
            this.loadListener = loadListener;
            return this;
        }

        public ImageConfig build() {
            return new ImageConfig(this);
        }


    }
}

在看看看使用的人是怎么使用的

ImageConfig config2 = ImageConfig.builder()
                    .url(UserAvatarUtil.getUrl(item.getZavatar()))
                    .placeholderImage(R.drawable.icon_set_avatar)
                    .failureImage(R.drawable.icon_set_avatar)
                    .style(ImageStyle.CIRCLE)
                    .build();
            ivAvatar.setImageWithConfig(config2);

五、Android源码中模式实现

在Android源码中,我们最常用到的Builder模式就是AlertDialog.Builder, 使用该Builder来构建复杂的AlertDialog对象。简单示例如下 :

//显示基本的AlertDialog  
private void showDialog(Context context) {  
    AlertDialog.Builder builder = new AlertDialog.Builder(context);  
    builder.setIcon(R.drawable.icon);  
    builder.setTitle("Title");  
    builder.setMessage("Message");  
    builder.setPositiveButton("Button1",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("点击了对话框上的Button1");  
                }  
            });  
    builder.setNeutralButton("Button2",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("点击了对话框上的Button2");  
                }  
            });  
    builder.setNegativeButton("Button3",  
            new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialog, int whichButton) {  
                    setTitle("点击了对话框上的Button3");  
                }  
            });  
    builder.create().show();  // 构建AlertDialog, 并且显示
} 

结果 : [图片上传失败...(image-928a13-1554990544119)]

下面我们看看AlertDialog的相关源码 :

// AlertDialog
public class AlertDialog extends Dialog implements DialogInterface {
    // Controller, 接受Builder成员变量P中的各个参数
    private AlertController mAlert;

    // 构造函数
    protected AlertDialog(Context context, int theme) {
        this(context, theme, true);
    }

    // 4 : 构造AlertDialog
    AlertDialog(Context context, int theme, boolean createContextWrapper) {
        super(context, resolveDialogTheme(context, theme), createContextWrapper);
        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = new AlertController(getContext(), this, getWindow());
    }

    // 实际上调用的是mAlert的setTitle方法
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }

    // 实际上调用的是mAlert的setCustomTitle方法
    public void setCustomTitle(View customTitleView) {
        mAlert.setCustomTitle(customTitleView);
    }
    
    public void setMessage(CharSequence message) {
        mAlert.setMessage(message);
    }

    // AlertDialog其他的代码省略
    
    // ************  Builder为AlertDialog的内部类   *******************
    public static class Builder {
        // 1 : 存储AlertDialog的各个参数, 例如title, message, icon等.
        private final AlertController.AlertParams P;
        // 属性省略
        
        /**
         * Constructor using a context for this builder and the {@link AlertDialog} it creates.
         */
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }


        public Builder(Context context, int theme) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, theme)));
            mTheme = theme;
        }
        
        // Builder的其他代码省略 ......

        // 2 : 设置各种参数
        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }
        
        
        public Builder setMessage(CharSequence message) {
            P.mMessage = message;
            return this;
        }

        public Builder setIcon(int iconId) {
            P.mIconId = iconId;
            return this;
        }
        
        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
            P.mPositiveButtonText = text;
            P.mPositiveButtonListener = listener;
            return this;
        }
        
        
        public Builder setView(View view) {
            P.mView = view;
            P.mViewSpacingSpecified = false;
            return this;
        }
        
        // 3 : 构建AlertDialog, 传递参数
        public AlertDialog create() {
            // 调用new AlertDialog构造对象, 并且将参数传递个体AlertDialog 
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
            // 5 : 将P中的参数应用的dialog中的mAlert对象中
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }
    }
    
}

可以看到,通过Builder来设置AlertDialog中的title, message, button等参数, 这些参数都存储在类型为AlertController.AlertParams的成员变量P中,AlertController.AlertParams中包含了与之对应的成员变量。在调用Builder类的create函数时才创建AlertDialog, 并且将Builder成员变量P中保存的参数应用到AlertDialog的mAlert对象中,即P.apply(dialog.mAlert)代码段。我们看看apply函数的实现 :

public void apply(AlertController dialog) {
    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);
    }
    if (mForceInverseBackground) {
        dialog.setInverseBackgroundForced(true);
    }
    // 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);
        }
    }
}

实际上就是把P中的参数挨个的设置到AlertController中, 也就是AlertDialog中的mAlert对象。从AlertDialog的各个setter方法中我们也可以看到,实际上也都是调用了mAlert对应的setter方法。在这里,Builder同时扮演了上文中提到的builder、ConcreteBuilder、Director的角色,简化了Builder模式的设计。

六、优缺点

优点

  • 良好的封装性, 使用建造者模式可以使客户端不必知道产品内部组成的细节;
  • 建造者独立,容易扩展;
  • 在对象创建过程中会使用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到。

缺点

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

推荐阅读更多精彩内容