【设计模式笔记】(二)- Builder模式

1.简述

Builder模式也就是建造者模式,先说定义,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

首先,将复杂对象的创建过程和部件的表示分离出来,其实就是把创建过程和自身的部件解耦,使得构建过程和部件都可以自由扩展,两者之间的耦合降到最低。然后,再是相同的构建过程可以创建不同的表示,相同的组合也可以通过不同的部件创建出不同的对象。

可能使用场景

  • 相同的方法,不同的执行顺序,产生不同的结果时
  • 多个部件(代码中就对应类的属性),都可以装配到一个对象中,但是产生的运行结果又不相同时
  • 初始化一个对象特别复杂,参数多且很多参数都具有默认值时

这只是举几个比较合适的例子而已

2.实现

  • Product —— 产品的抽象类
  • Builder —— 抽象Builder类,规范产品的组件
  • ConcreteBuilder —— 具体的Builder类,实现具体的组建过程
  • Director —— 统一组装过程

看到这里可能回觉得这个模式怎么和android平时使用的不太一样啊!Builder一般不是只有Product和Builder么?

关于这点,有度娘过一下Builder模式,基本介绍都是这种结构,相对标准,这个标准的意思就是帮助学习该设计模式,并不是指一种用法,毕竟设计模式还是需要灵活使用。而没有Director该环节的实现方式就是对Builder模式的一种简化。

这是一个手机配置简化的抽象类,设置CPU、OS以、内存大小以及运存大小

public abstract class Phone {
    protected String mCPU;
    protected String mOS;
    protected int mMemorySize;
    protected int mStorageSize;

    public abstract void setCPU();

    public abstract void setOS();

    public void setMemorySize(int memorySize) {
        this.mMemorySize = memorySize;
    }

    public void setStorageSize(int storageSize) {
        this.mStorageSize = storageSize;
    }

    @Override
    public String toString() {
        return "{ \n CPU : " + mCPU + "\n OS : " + mOS + 
            " \n MemorySize : " + mMemorySize + "GB" + 
            " \n StorageSize : " + mStorageSize + "GB \n}";
    }
}

具体的IPhoneX的类,由于CPU和系统是固定,而内存和运存运存可选。

public class IPhoneX extends Phone {

    public  IPhoneX(){}

    @Override
    public void setCPU() {
        mCPU = "A11";
    }

    @Override
    public void setOS() {
        mOS = "iOS 11";
    }
}

抽象的Builder类,作为主要隔离作用的类,Phone的API的每一个方法都有对应的build方法,并都返回自身来实现链式API。

public abstract class Builder {
    //设置CPU
    public abstract Builder buildCPU();
    //设置系统
    public abstract Builder buildOS();
    //设置运存大小
    public abstract Builder buildMemorySize(int memorySize);
    //设置储存大小
    public abstract Builder buildStorageSize(int storageSize);
    //创建一个Phone对象
    public abstract Phone create();
}

IPhoneXBuilder,具体的Builder

public class IPhoneXBuilder extends Builder {

    private IPhoneX mIPhoneX = new IPhoneX();

    @Override
    public Builder buildCPU() {
        mIPhoneX.setCPU();
        return this;
    }

    @Override
    public Builder buildOS() {
        mIPhoneX.setOS();
        return this;
    }

    @Override
    public Builder buildMemorySize(int memorySize) {
        mIPhoneX.setMemorySize(memorySize);
        return this;
    }

    @Override
    public Builder buildStorageSize(int storageSize) {
        mIPhoneX.setStorageSize(storageSize);
        return this;
    }

    @Override
    public Phone create() {
        return mIPhoneX;
    }
}

Director类,负责构造Phone

public class Director {

    Builder mBuilder = null;

    public Director(Builder builder){
        mBuilder = builder;
    }

    public void construct(int memorySize,int storageSize){
        mBuilder.buildCPU()
            .buildOS()
            .buildMemorySize(memorySize)
            .buildStorageSize(storageSize);
    }
}

下面是测试代码

//构建器
Builder builder = new IPhoneXBuilder();
//Director
Director director = new Director(builder);
//封装构建过程
director.construct(6,256);
//构建Phone,输出相关信息
Log.i(TAG, builder.create().toString());

通过具体的IPhoneXBuilder类构建IPhoneX对象,Director封装了构建Phone对象的过程,隐藏构建的细节。BuilderDirector两个部分起到了将对象的构建过程和对象的表示分离的作用。

之前也提到过,真是开发中Director类一般会省略,直接使用Builder,采用链式API进行组装,在使用Buidler模式的过程中更加有效。

3.Android源码中得Builder模式实现

首先想到的就是AlertDialog.Builder,AlertDialog.Builder其实是AlertDialog的静态内部类,所有的dialog属性都暂存在AlertController.AlertParams的一个final对象中,以此来做到一个Builder对象只组装一次,却能构建出多个属性相同的dialog。

这里截取了AlertDialog中比较关键的代码,

public class AlertDialog extends AppCompatDialog implements DialogInterface {

    final AlertController mAlert;

    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        //构造AlertController对象
        mAlert = new AlertController(getContext(), this, getWindow());
    }
    
    //此处省略不知道多少代码。。。
    
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }
    
    //******* 内部类Builder *******
    public static class Builder {
        //存储AlertDialog的相关属性
        private final AlertController.AlertParams P;
        private final int mTheme;
        
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }
        
        //...
        
        public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }
        
        //...
        
        public AlertDialog create() {
            // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
            // so we always have to re-set the theme
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);

            //将设置到Builder中得属性设置到dialog的mAlert中,再由mAlert设置到view上
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }

        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}

还有一点值得关注,AlertDialog中的AlertController对象,是接收Builder成员变量AlertController.AlertParams中的各个参数。AlertControllerAlertController.AlertParams不太相同的地方是,AlertController中又真是的View和属性设置,而AlertController.AlertParams只是作为一个暂时存放属性值得对象,在apply()方法调用时将相关的属性值传递给AlertDialogAlertController对象,再由AlertController`对象设置到view上。

4.总结

Builder模式在Android开发中也是比较常用的,通常作为配置类的构建器,将配置的构建和表示分离,同时也是将配置从使用中隔离出来,避免过多的setter方法。在Builder模式的实现中经常通过链式调用实现,达到通俗易懂的目的。

优点:

  • 良好的封装性,使用者不用知道内部的实现细节
  • 容易扩展,由于Builder的独立存在扩展不会影响原有逻辑

缺点:

  • 会产生多余的Builder对象,可能还有Director对象,占用内存

像网络框架(或图片加载框架)的Config的构建,也是Builder模式所适用的场景。总的来说,设计模式还是需要明白设计的主要目的,才能更好地使用各种模式去解决实际问题。

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

推荐阅读更多精彩内容