设计模式之二:Builder模式

设计模式之二:Builder模式
目录介绍
0.关于Builder模式案例下载
1.Builder模式介绍
2.Builder模式使用场景
3.Builder模式简单案例
3.1 Builder模式UML图(摘自网络)
3.2 在《Android源码设计模式》这本书上,介绍经典Builder模式中,包括
3.3 Product角色
3.4 Builder : 抽象Builder类,规范产品组建,一般是由子类实现具体的组建过程
3.5 ConcreteBuilder : 具体的Builder类
3.6 Director : 统一组装过程
4.Builder模式实际案例Demo
4.1 实际开发中,有时会遇到复杂的对象的代码
4.2 通过构造函数的参数形式去写一个实现类,或者通过set,get去实现
4.3 分析
4.4 将上面例子改成builder模式如下所示
4.5 最后运用,代码如下
4.6 关于线程安全问题
5.Android源码中的Builder模式实现
5.1 首先看看AlertDialog.Builder源代码,只是摘自部分代码
5.2 接下来看看build源代码
5.3 接下来看看show的代码
5.4 为什么AlertDialog要使用builder模式呢?
6.Builder模式总结
6.1 builder模式优点
6.2 builder模式缺点
7.关于其他说明
7.1 参考案例
8.关于其他更多内容

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

1.Builder模式介绍

1.1 定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的展示。
1.2 Builder模式属于创建型,一步一步将一个复杂对象创建出来,允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。

2.Builder模式使用场景

相同方法,不同执行顺序,产生不同事件结果
初始化对象特别复杂,参数众多,且很多参数都具有默认值时

3.Builder模式简单案例

3.1 Builder模式UML图(摘自网络)

  • Image.png

3.2 在《Android源码设计模式》这本书上,介绍经典Builder模式中,包括

  • Product : 产品抽象类
  • Builder : 抽象Builder类,规范产品组建,一般是由子类实现具体的组建过程
  • ConcreteBuilder : 具体的Builder类
  • Director : 统一组装过程

3.3 Product角色

//设置抽象类
public abstract class BuilderUser {
    protected String name;
    protected String cardID;
    protected int age;
    protected String address;
    public BuilderUser() {}
    //设置名字
    public void setName(String name) {
        this.name = name;
    }

    //抽象方法
    public abstract void setCardID();

    //设置年龄
    public void setAge(int age) {
        this.age = age;
    }

    //设置地址
    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "BuilderUser{" +
                "name='" + name + '\'' +
                ", cardID='" + cardID + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}
/**
 * 具体的Product角色 UserCard
 */
public class UserCard extends BuilderUser {

    public UserCard() {}

    @Override
    public void setCardID() {
        cardID="10086";             //设置默认ID
    }
}

3.4 Builder : 抽象Builder类,规范产品组建,一般是由子类实现具体的组建过程

/**
 * 抽象Builder类
 */
public abstract class Builder {
    public abstract void buildName(String name);
    public abstract void buildCardID();
    public abstract void buildAge(int age);
    public abstract void buildAddress(String address);
    public abstract BuilderUser create();
}

3.5 ConcreteBuilder : 具体的Builder类

/**
* 具体的Builder类
*/
public class AccountBuilder extends Builder{

    private BuilderUser user = new UserCard();

    @Override
    public void buildName(String name) {
        user.setName(name);
    }

    @Override
    public void buildCardID() {
        user.setCardID();
    }

    @Override
    public void buildAge(int age) {
        user.setAge(age);
    }

    @Override
    public void buildAddress(String address) {
        user.setAddress(address);
    }

    @Override
    public BuilderUser create() {
        return user;
    }
}

3.6 Director : 统一组装过程

/**
* Director角色,负责构造User
*/
public class Director {
    Builder mBuilder =null;

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

    public void construct(String name,int age,String address){
        mBuilder.buildName(name);
        mBuilder.buildCardID();
        mBuilder.buildAge(age);
        mBuilder.buildAddress(address);
    }
}

4.Builder模式实际案例Demo

4.1 实际开发中,有时会遇到复杂的对象的代码,比如

public class BuilderDemo {
    private final String name;         //必选
    private final String cardID;       //必选
    private final int age;             //可选
    private final String address;      //可选
    private final String phone;        //可选
}

4.2 通过构造函数的参数形式去写一个实现类,或者通过set,get去实现

BuilderDemo(String name);
BuilderDemo(String name, String cardID);
BuilderDemo(String name, String cardID,int age);
BuilderDemo(String name, String cardID,int age, String address);
BuilderDemo(String name, String cardID,int age, String address,String phone);

4.3 分析

  • 第一种在参数不多的情况下,是比较方便快捷的,一旦参数多了,代码可读性大大降低,并且难以维护,对调用者来说也造成一定困惑;
  • 第二种可读性不错,也易于维护,但是这样子做对象会产生不一致的状态,当你想要传入全部参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实User对象并没有创建完成,另外,这个User对象也是可变的,不可变类所有好处都不复存在。
  • Builder模式同时满足上面两种情况,易于维护,并且创建方便

4.4 将上面例子改成builder模式如下所示

public class BuilderDemo {

    private final String name;         //必选
    private final String cardID;       //必选
    private final int age;             //可选
    private final String address;      //可选
    private final String phone;        //可选

    public BuilderDemo(UserBuilder userBuilder){
        this.name=userBuilder.name;
        this.cardID=userBuilder.cardID;
        this.age=userBuilder.age;
        this.address=userBuilder.address;
        this.phone=userBuilder.phone;
    }

    public static class UserBuilder{
        private final String name;
        private final String cardID;
        private int age;
        private String address;
        private String phone;

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

        public UserBuilder(String name,String cardID){
            this.name=name;
            this.cardID=cardID;
        }

        public UserBuilder age(int age){
            this.age=age;
            return this;
        }

        public UserBuilder address(String address){
            this.address=address;
            return this;
        }

        public UserBuilder phone(String phone){
            this.phone=phone;
            return this;
        }
    }
}

4.5 最后运用,代码如下

new BuilderDemo.UserBuilder("yc","10086")
        .age(24)
        .address("beijing")
        .phone("13667225184")
        .build();

4.6 关于线程安全问题

// 线程安全
public BuilderDemo build(){
    BuilderDemo builderDemo = new BuilderDemo(this);
    if(builderDemo.age > 100){
        throw new IllegalStateException("Age out of range");
    }
    return builderDemo;
}

// 线程不安全,不要这样写
public BuilderDemo build(){
    if(age > 100){
        throw new IllegalStateException("Age out of range");
    }
    return new BuilderDemo(this);
}

5.Android源码中的Builder模式实现

5.1 首先看看AlertDialog.Builder源代码,只是摘自部分代码

public class AlertDialog extends AppCompatDialog implements DialogInterface {

    //接收builder成员变量p中各个参数
    final AlertController mAlert;

    //下面是三个构造函数
    protected AlertDialog(@NonNull Context context) {
        this(context, 0);
    }

    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
    }

    protected AlertDialog(@NonNull Context context, boolean cancelable,
            @Nullable OnCancelListener cancelListener) {
        this(context, 0);
        setCancelable(cancelable);
        setOnCancelListener(cancelListener);
    }

    //事件上这里调用了是mAlert的setView方法
    public void setView(View view) {
        mAlert.setView(view);
    }

    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
            int viewSpacingBottom) {
        mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
    }

    public static class Builder {
        //这个里面存放很多参数
        private final AlertController.AlertParams P;

        //这个是new AlertDialog.Builder(context,R.style.AppTheme)调用的方法
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }

        //这部分代码省略很多,主要是set设置各种参数
        public Builder setTitle(@Nullable CharSequence title) {
            P.mTitle = title;
            return this;
        }

              //这部分代码省略很多,主要是set设置各种参数
        public Builder setView(int layoutResId) {
            P.mView = null;
            P.mViewLayoutResId = layoutResId;
            P.mViewSpacingSpecified = false;
            return this;
        }

        //构建dialog,传递参数
        public AlertDialog create() {
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            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;
        }

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

5.2 接下来看看build源代码

  • 其中P是一个保存设置AlterDialog的参数类AlertParams的对象。很显然,Builder只是将AlterDialog的参数存放在一个参数对象里,做一个暂时的保存。在最后调用create方法的时候,创建一个AlterDialog对象,将暂存的参数P一次性应用到这个Dialog对象上。完成Dialog的创建。
    当然,这只是创建AlertDialog,还没show出来。可以直接调用刚创建的AlertDialog的show()方法(实际为调用父类Dialog的show()),也可以在构建完Builder之后直接调用Builder的show方法
public AlertDialog create() {
    //创建
    final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
    //这一步很重要,下面会单独分析
    P.apply(dialog.mAlert);
    //设置是否可以取消,默认是true
    dialog.setCancelable(P.mCancelable);
    if (P.mCancelable) {
        dialog.setCanceledOnTouchOutside(true);
    }
    //设置取消监听
    dialog.setOnCancelListener(P.mOnCancelListener);
    //设置dialog关闭后监听
    dialog.setOnDismissListener(P.mOnDismissListener);
    //设置返回键监听
    if (P.mOnKeyListener != null) {
        dialog.setOnKeyListener(P.mOnKeyListener);
    }
    //返回对象,创建成功
    return dialog;
}

5.3 接下来看看show的代码

public void show() {
    //如果弹窗已经show出来了 
    if (mShowing) {
        //如果顶级view存在则设置窗口window,并显示顶级View 
        if (mDecor != null) {
            if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
            }
            mDecor.setVisibility(View.VISIBLE);
        }
        return;
    }

    mCanceled = false;
    //如果窗口还未被创建 
    if (!mCreated) {
        dispatchOnCreate(null);
    }
    //获得Windowd的顶级View,并将其添加到对应的WindowManager中,然后发送show的消息 
    onStart();
    //获取DecorView
    mDecor = mWindow.getDecorView();

    if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
        final ApplicationInfo info = mContext.getApplicationInfo();
        mWindow.setDefaultIcon(info.icon);
        mWindow.setDefaultLogo(info.logo);
        mActionBar = new WindowDecorActionBar(this);
    }
    //获取布局参数
    WindowManager.LayoutParams l = mWindow.getAttributes();
    if ((l.softInputMode
            & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
        WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
        nl.copyFrom(l);
        nl.softInputMode |=
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        l = nl;
    }

    try {
        //将mDecor添加到WindowManager中
        mWindowManager.addView(mDecor, l);
        mShowing = true;
        //发送一个显示Dialog消息
        sendShowMessage();
    } finally {
    }
}
  • 在show函数中主要做了如下的事情:
    (1)通过dispatchOnCreate函数来调用AlertDialog的onCreate函数
    (2)然后接着调用onStart函数
    (3)最后将Dialog的mDecor添加到WindowManager中(有点不明白这里,为什么要获取到最顶部的布局)

5.4 为什么AlertDialog要使用builder模式呢?

  • AlterDialog有很多的参数,如我们上面列举的title,message,三个button及其回调,除此以外还有icon,View等属性。如果Android不采用Builder,而采用普通的构造函数来设计AlterDialog,可能要写出很多类似于如下的构造函数:
AlertDialog(String title);
AlertDialog(String message)
AlertDialog(int resId)
AlertDialog(int resId, String title, String message);
AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener);
AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener);
AlterDialog(int resId, String title, String message, String NegativeButtonString, OnClickListener listener);
AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener, String NegativeButtonString, OnClickListener listener);
....

6.Builder模式总结

6.1 Builder模式有几个好处:

  1. Builder的setter函数可以包含安全检查,可以确保构建过程的安全,有效。
  2. Builder的setter函数是具名函数,有意义,相对于构造函数的一长串函数列表,更容易阅读维护。
  3. 可以利用单个Builder对象构建多个对象,Builder的参数可以在创建对象的时候利用setter函数进行调整

6.2 当然Builder模式也有缺点:

  1. 更多的代码,需要Builder这样的内部类
  2. 增加了类创建的运行时开销 ,但是当一个类参数很多的时候,Builder模式带来的好处要远胜于其缺点。

7.关于其他说明

7.1 参考案例

  • 参考《Android源码设计解析与实战 》

8.关于其他更多内容

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,681评论 18 139
  • 1 场景问题# 1.1 继续导出数据的应用框架## 在讨论工厂方法模式的时候,提到了一个导出数据的应用框架。 对于...
    七寸知架构阅读 5,749评论 1 64
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,777评论 2 17
  • 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于...
    JxMY阅读 946评论 1 3
  • 我要让你喜欢我,却说不清楚为什么喜欢我。
    Thelma白子先行阅读 160评论 0 0