设计模式之二: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. phpwind 缓存的问题

  2. 使用了未经检查或不安全的操作。 有关详细信息, 请使用 -Xlint:unchecked 重新编译

  3. Hbase-执行hbase shell命令时提示:ERROR: KeeperErrorCode = NoNode for /hbase/master

    1.问题描述 执行hbase  shell命令时提示: ERROR: KeeperErrorCode = NoNode for /hbase/master 2.问题原因 这是与因为服务器重启后Hado ...

  4. mac os 解决Error: EMFILE: too many open files错误

    壹 ❀ 引 构建项目时终端反复出现Error: EMFILE: too many open files的错误,经排查是因为项目较大,发布过程中已经超过了mac默认的文件监听上限,错误如下: 解决方式也 ...

  5. CF1823D Unique Palindromes

    题目链接 题解 知识点:构造. 首先反证法容易证明一个结论:每次增加一个字符,本质不同的回文子串至多增加一个. 那么无解的条件就是,\(c_i - c_{i-1} > x_i -x_{i-1}\ ...

  6. Git识别文件权限修改

    刚打开IDE,工作区的代码状态全部变成修改未提交的状态了?这是这么回事?这是因为Git忽略文件权限或者拥有者改变导致的git状态变化.默认Git会记录文件的权限信息,如果文件的权限信息被修改,在Git ...

  7. Shiro实战2-Springboot集成Shiro实战

    说明 最近打算打造一个简易的权限管理系统,打算用shiro做认证和授权.本篇作为springboot集成shiro的入门实战案例记录下来,希望也可以帮到大家,代码整理自网络. 技术栈 springbo ...

  8. Oracle如何限制非法调用包中过程

    原文:http://www.oracle.com/technetwork/issue-archive/2015/15-jan/o15plsql-2398996.html 假如我有一个包P_A,其中封装 ...

  9. Web流式下载数据时展示提示信息

    以Web方式下载数据有多种场景: 1.服务端本身已经存在文件,此时只需要一个文件访问地址即可下载,比如:将文件URL设置为<a>标签的href属性即可,点击<a>标签就能立即触 ...

  10. 用Taro写一个微信小程序——渲染 HTML

    在taro小程序里面渲染一段html代码,具体可查看文档https://docs.taro.zone/docs/html 一.渲染 HTML 文档推荐使用 dangerouslySetInnerHTM ...