设计模式之二: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.关于其他更多内容
 
 
1.Builder模式介绍
  • 1.1 定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的展示。
  • 1.2 Builder模式属于创建型,一步一步将一个复杂对象创建出来,允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。
 
2.Builder模式使用场景
  • 相同方法,不同执行顺序,产生不同事件结果
  • 初始化对象特别复杂,参数众多,且很多参数都具有默认值时
 
3.Builder模式简单案例
  • 3.1 Builder模式UML图(摘自网络)
  • 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模式总结
  • Builder模式有几个好处:
    • 1. Builder的setter函数可以包含安全检查,可以确保构建过程的安全,有效。
    • 2. Builder的setter函数是具名函数,有意义,相对于构造函数的一长串函数列表,更容易阅读维护。
    • 3. 可以利用单个Builder对象构建多个对象,Builder的参数可以在创建对象的时候利用setter函数进行调整
  • 当然Builder模式也有缺点:
    • 1. 更多的代码,需要Builder这样的内部类
    • 2. 增加了类创建的运行时开销 ,但是当一个类参数很多的时候,Builder模式带来的好处要远胜于其缺点。
 
7.关于其他说明
  • 7.1 参考案例
    • 参考《Android源码设计解析与实战 》
 
8.关于其他更多内容
脉脉:yc930211
邮箱:yangchong211@163.com

设计模式之二:Builder模式的更多相关文章

  1. Java进阶篇设计模式之二 ----- 工厂模式

    前言 在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模 ...

  2. Java设计模式之二 ----- 工厂模式

    在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模式,又 ...

  3. Java设计模式之二工厂模式

    在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模式,又 ...

  4. 设计模式之生成器(Builder)模式

    意图 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以表示不同的表示. 适用性 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时. 当构造过程必须允许被构造的对象有不同的表 ...

  5. Java学习笔记——设计模式之二.策略模式

    明确是王道 --Clean Code 先定义策略类 package cn.no2.strategy; public abstract class Strategy { //省略属性 //算法方法 pu ...

  6. Builder模式在Java中的应用

    在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...

  7. Builder模式(建造者模式)

    在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...

  8. Builder模式在Java中的应用(转)

    在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...

  9. 设计模式(二): BUILDER生成器模式 -- 创建型模式

    1.定义 将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式. 2.适用场景 1. 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式 ...

  10. Android开发中常见的设计模式(二)——Builder模式

    了解了单例模式,接下来介绍另一个常见的模式--Builder模式. 那么什么是Builder模式呢.通过搜索,会发现大部分网上的定义都是 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建 ...

随机推荐

  1. delphi IDE 代码 恢复

  2. Python numpy数组操作(分割数组)

    分割数组 函数 数组及操作 split 将一个数组分割为多个子数组 hsplit 将一个数组水平分割为多个子数组(按列) vsplit 将一个数组垂直分割为多个子数组(按行) numpy.split ...

  3. OI中的一些数学小技巧

    在OI比赛中,如果能够灵活地运用一些数学小技巧,是能够很好地优化计算的时间和正确性的. 既然说了是小技巧,那么这些指的都是一些技巧,一般是不会单独成题的. 本博客或会随着作者的见识而更新 Better ...

  4. Linux线程 | 创建 终止 回收 分离

    一.线程简介 线程是参与系统调度的最小单位.它被包含在进程之中,是进程中的实际运行单位. 一个进程中可以创建多个线程,多个线程实现并发运行,每个线程执行不同的任务. 每个线程都有其对应的标识,称为线程 ...

  5. 从 KeyStore 中获取 PublicKey 和 PrivateKey

    KeyStore(译:密钥存储库) 代表用于加密密钥和证书的存储设施. KeyStore 管理不同类型的 entry(译:条目).每种类型的 entry 都实现了 KeyStore.Entry 接口. ...

  6. Vue+SpringBoot+ElementUI实战学生管理系统-5.用户管理模块

    1.章节介绍 前一篇介绍了项目的API接口设计,这一篇编写用户管理模块,需要的朋友可以拿去自己定制.:) 2.获取源码 源码是捐赠方式获取,详细请QQ联系我 :)! 3.项目截图 列表操作 动态图 4 ...

  7. 我的小程序之旅七:微信公众号设置IP白名单

    一.为什么要配置IP白名单 此处IP为服务器对公网IP: 在IP白名单内的IP地址作为来源,获取access_token接口才可调用成功. 而想要调用公众号相关API,就必须获取access_toke ...

  8. SQL Server初体验

    概述 基于SQL Server 2019 Developer免费版搭建一个本地的开发环境. 下载安装 安装文件下载地址:https://www.microsoft.com/zh-cn/sql-serv ...

  9. 记一次酣畅淋漓的 K8s Ingress 排错过程(302,404,503,...)

    故事开始 第 1 关:[流量重定向到 /] 第 2 关:[应用返回 302,重定向到 /,引入 503 错误] 第 3 关:[静态资源访问遇到 503 问题] 第 4 关:[静态资源访问遇到 403 ...

  10. ElkStack-MACOS搭建

    目录 简介 Elasticsearch Logstash/Filebeats Kibana 架构流程 安装配置 版本 先决条件 brew Elasticsearch mac安装elasticsearc ...