设计模式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对象,消耗内存;
  • 对象的构建过程暴露。

你可能感兴趣的:(设计模式2-Builder建造者模式 提高易用性,减少使用困扰)