加油.png

前言:最近闲来无事的时候想着看看一些平常用的三方库源码,没想到看了之后才知道直接撸源码好伤身体,一般设计优秀的开源库都会涉及很多的设计模式,就比如 android 开发使用频繁的 okHttp 打开源码一看,纳尼?Builder 模式随处可见,于是乎,这篇文章就来对 Builder 模式进行一个简单总结,主要针对便于分析 android 相关源码,以实际应用出发~

在 oop 编码设计中,我们有句经典的话叫做 "万物皆对象".实际开发中,我们只要能拿到类的实例,即对象。就可以开始搞事情啦,可以命令对象去做一些事情,当然啦~每个对象的能力都是不同的,能做的事情也是不同。对象中存储着类的成员属性(成员变量和成员方法)。我们命令对象去为我们工作,其实就是调用对象特有的属性。刚刚我们也说了,每个对象的能力是不同的,对象所能做的事情,在一开始被创建的时候就决定了。下面先来说一下对象的构建方法。

一、通过构造器构建

假设一个场景:我们用一个class来表示车,车有一些必需的属性,比如:车身,轮胎,发动机,方向盘等。也有一些可选属性,假设超过10个,比如:车上的一些装饰,安全气囊等等非常多的属性。

如果我们用构造器来构造对象,我们的做法是 提供第一个包含4个必需属性的构造器,接下来再按可选属性依次重载不同的构造器,这样是可行的,但是会有以下一些问题:

  • 一旦属性非常多,需要重载n多个构造器,而且各种构造器的组成都是在特定需求的情况下制定的,代码量多了不说,灵活性大大下降
  • 客户端调用构造器的时候,需要传的属性非常多,可能导致调用困难,我们需要去熟悉每个特定构造器所提供的属性是什么样的,而参数属性多的情况下,我们可能因为疏忽而传错顺序。
 1public class Car {
2    /**
3     * 必需属性
4     */
5    private String carBody;//车身
6    private String tyre;//轮胎
7    private String engine;//发动机
8    private String aimingCircle;//方向盘
9    /**
10     * 可选属性
11     */
12    private String decoration;//车内装饰品
13
14    /**
15     * 必需属性构造器
16     *
17     * @param carBody
18     * @param tyre
19     * @param engine
20     */
21    public Car(String carBody, String tyre, String engine) {
22        this.carBody = carBody;
23        this.tyre = tyre;
24        this.engine = engine;
25    }
26
27    /**
28     * 假如我们需要再添加车内装饰品,即在原来构造器基础上再重载一个构造器
29     *
30     * @param carBody
31     * @param tyre
32     * @param engine
33     * @param aimingCircle
34     * @param decoration
35     */
36    public Car(String carBody, String tyre, String engine, String aimingCircle, String decoration) {
37        this.carBody = carBody;
38        this.tyre = tyre;
39        this.engine = engine;
40        this.aimingCircle = aimingCircle;
41        this.decoration = decoration;
42    }
43}

二、JavaBeans模式构建

提供无参的构造函数,暴露一些公共的方法让用户自己去设置对象属性,这种方法较之第一种似乎增强了灵活度,用户可以根据自己的需要随意去设置属性。但是这种方法自身存在严重的缺点:
因为构造过程被分到了几个调用中,在构造中 JavaBean 可能处于不一致的状态。类无法仅仅通过判断构造器参数的有效性来保证一致性。还有一个严重的弊端是,JavaBeans 模式阻止了把类做成不可变的可能。,这就需要我们付出额外的操作来保证它的线程安全。

 1public class Car {
2    /**
3     * 必需属性
4     */
5    private String carBody;//车身
6    private String tyre;//轮胎
7    private String engine;//发动机
8    private String aimingCircle;//方向盘
9    /**
10     * 可选属性
11     */
12    private String decoration;//车内装饰品
13
14    public void setCarBody(String carBody) {
15        this.carBody = carBody;
16    }
17
18    public void setTyre(String tyre) {
19        this.tyre = tyre;
20    }
21
22    public void setEngine(String engine) {
23        this.engine = engine;
24    }
25
26    public void setAimingCircle(String aimingCircle) {
27        this.aimingCircle = aimingCircle;
28    }
29
30    public void setDecoration(String decoration) {
31        this.decoration = decoration;
32    }
33}

那么有没有什么方法可以解决以上问题呢?当然有啦~下面我们的主角上场-----Builder 模式

三、Builder 模式

我们用户一般不会自己来完成 car 组装这些繁琐的过程,而是把它交给汽车制造商。由汽车制造商去完成汽车的组装过程,这里的 Builder 就是汽车制造商,我们的 car 的创建都交由他来完成,我们只管开车就是啦, 先来个代码实际体验一下~

 1public final class Car {
2    /**
3     * 必需属性
4     */
5    final String carBody;//车身
6    final String tyre;//轮胎
7    final String engine;//发动机
8    final String aimingCircle;//方向盘
9    final String safetyBelt;//安全带
10    /**
11     * 可选属性
12     */
13    final String decoration;//车内装饰品
14    /**
15     * car 的构造器 持有 Builder,将builder制造的组件赋值给 car 完成构建
16     * @param builder
17     */
18    public Car(Builder builder) {
19        this.carBody = builder.carBody;
20        this.tyre = builder.tyre;
21        this.engine = builder.engine;
22        this.aimingCircle = builder.aimingCircle;
23        this.decoration = builder.decoration;
24        this.safetyBelt = builder.safetyBelt;
25    }
26    ...省略一些get方法
27    public static final class Builder {
28        String carBody;
29        String tyre;
30        String engine;
31        String aimingCircle;
32        String decoration;
33        String safetyBelt;
34
35        public Builder() {
36            this.carBody = "宝马";
37            this.tyre = "宝马";
38            this.engine = "宝马";
39            this.aimingCircle = "宝马";
40            this.decoration = "宝马";
41        }
42         /**
43         * 实际属性配置方法
44         * @param carBody
45         * @return
46         */
47        public Builder carBody(String carBody) {
48            this.carBody = carBody;
49            return this;
50        }
51
52        public Builder tyre(String tyre) {
53            this.tyre = tyre;
54            return this;
55        }
56        public Builder safetyBelt(String safetyBelt) {
57          if (safetyBelt == null) throw new NullPointerException("没系安全带,你开个毛车啊");
58            this.safetyBelt = safetyBelt;
59            return this;
60        }
61        public Builder engine(String engine) {
62            this.engine = engine;
63            return this;
64        }
65
66        public Builder aimingCircle(String aimingCircle) {
67            this.aimingCircle = aimingCircle;
68            return this;
69        }
70
71        public Builder decoration(String decoration) {
72            this.decoration = decoration;
73            return this;
74        }
75        /**
76         * 最后创造出实体car
77         * @return
78         */
79        public Car build() {
80            return new Car(this);
81        }
82    }
83}

现在我们的类就写好了,我们调用的时候执行一下代码:

1 Car car = new Car.Builder()
2                .build();

打断点,debug运行看看效果:

car默认构造.png

可以看到,我们默认的 car 已经制造出来了,默认的零件都是 "宝马",滴滴滴~来不及解释了,快上车。假如我们不使用默认值,需要自己定制的话,非常简单。只需要拿到 Builder 对象之后,依次调用指定方法,最后再调用 build 返回 car 即可。下面代码示例:

1        //配置car的车身为 奔驰
2        Car car = new Car.Builder()
3                .carBody("奔驰")
4                .build();

依旧 debug 看看 car 是否定制成功~

car 定制.png

咦,神奇的定制 car 定制成功了,话不多说,继续开车~~

我们在 Builder 类中的一系列构建方法中还可以加入一些我们对配置属性的限制。例如我们给 car 添加一个安全带属性,在 Buidler 对应方法出添加以下代码:

1 public Builder safetyBelt(String safetyBelt) {
2            if (safetyBelt == null) throw new NullPointerException("没系安全带,你开个毛车啊");
3            this.safetyBelt = safetyBelt;
4            return this;
5        }

然后调用的时候:

1     //配置car的车身为 奔驰
2     Car car = new Car.Builder()
3                      .carBody("奔驰")
4                      .safetyBelt(null)
5                      .build();

我们给配置安全带属性加了 null 判断,一但配置了null 属性,即会抛出异常。好了 car 构建好了,我们来开车看看~

依旧 debug 开车走起~

car 属性配置判断.png

bom~~~不出意外,翻车了。。。

最后有客户说了,你制造出来的 car 体验不是很好,想把车再改造改造,可是车已经出厂了还能改造吗?那这应该怎么办呢?不要急,好说好说,我们只要能再拿到 Builder 对象就有办法。下面我们给 Builder 添加如下构造,再对比下 Car 的构造看看有啥奇特之处:

 1       /**
2         * 回厂重造
3         * @param car
4         */
5        public Builder(Car car) {
6            this.carBody = car.carBody;
7            this.safetyBelt = car.safetyBelt;
8            this.decoration = car.decoration;
9            this.tyre = car.tyre;
10            this.aimingCircle = car.aimingCircle;
11            this.engine = car.engine;
12        }
13  /**
14     * car 的构造器 持有 Builder,将 builder 制造的组件赋值给 car 完成构建
15     *
16     * @param builder
17     */
18    public Car(Builder builder) {
19        this.carBody = builder.carBody;
20        this.tyre = builder.tyre;
21        this.engine = builder.engine;
22        this.aimingCircle = builder.aimingCircle;
23        this.decoration = builder.decoration;
24        this.safetyBelt = builder.safetyBelt;
25    }

咦,似乎有着对称的关系,没错。我们提供对应的构造。调用返回对应的对象,可以实现返回的效果。在 Car 中添加方法

1 /**
2     * 重新拿回builder 去改造car
3     * @return
4     */
5    public Builder newBuilder() {
6        return new Builder(this);
7    }

现在来试试能不能返厂重建?把原来的宝马车重造成奔驰车,调用代码:

1Car newCar = car.newBuilder()
2                .carBody("奔驰")
3                .safetyBelt("奔驰")
4                .tyre("奔驰")
5                .aimingCircle("奔驰")
6                .decoration("奔驰")
7                .engine("奔驰")
8                .build();

行,车改造好了,我们继续 debug ,试试改造完满不满意

car 改造.png
哈哈,已经改造好了,客户相当满意~~

下面分析一下具体是怎么构建的。

  • 新建静态内部类 Builder ,也就是汽车制造商,我们的 car 交给他来制造,car 需要的属性 全部复制进来
  • 定义 Builder 空构造,初始化 car 默认值。这里是为了初始化构造的时候,不要再去特别定义属性,直接使用默认值。定义 Builder 构造,传入 Car ,构造里面执行 Car 属性赋值 给 Builder 对应属性的操作,目的是为了重建一个builder 进行返厂重造
  • 定义一系列方法进行属性初始化,这些方法跟 JavaBeans 模式构建 中的方法类似,不同的是,返回值为 Builder 类型,为了方便链式调用。最后定义方法返回实体 Car 对象,car 的构造器 持有 Builder,最终将builder制造的组件赋值给 car 完成构建

至此,我们的 Builder 模式体验就结束了,这里讲的只是 Builder 模式的一个变种,即在 android 中应用较为广泛的模式,下面总结一下优缺点:

优点

  • 解耦,逻辑清晰。统一交由 Builder 类构造,Car 类不用关心内部实现细节,只注重结果。

  • 链式调用,使用灵活,易于扩展。相对于方法一中的构造器方法,配置对象属性灵活度大大提高,支持链式调用使得逻辑清晰不少,而且我们需要扩展的时候,也只需要添加对应扩展属性即可,十分方便。

缺点

  • 硬要说缺点的话 就是前期需要编写更多的代码,每次构建需要先创建对应的 Builder 对象。但是这点开销几乎可以忽略吧,前期编写更多的代码是为了以后更好的扩展,这不是优秀程序员应该要考虑的事么

解决方法: 不会偷懒的程序猿不是好程序猿,针对以上缺点,IDEA 系列的 ide ,有相应的插件 InnerBuilder 可以自动生成 builder 相关代码,安装自行 google,使用的时候只需要在实体类中 alt + insert 键,会有个 build 按钮提供代码生成。

使用场景
一般如果类属性在4个以上的话,建议使用 此模式。还有如果类属性存在不确定性,可能以后还会新增属性时使用,便于扩展。

四、Builder 模式在 android 中的应用

1. 在 okHttp 中广泛使用

开篇我们也说到了 Builder 模式在 okHttp 中随处可见。比如在OkHttpClient,Request,Response 等类都使用了此模式。下面以
Request 类为例简要说明,具体的可以去下载源码查看,按照上面的套路基本没问题。

Request 有6个属性,按照套路 构造方法持有一个 Builder ,在构造中将 builder 制造的组件赋值给 Request 完成构建,提供 newBuilder 用于重新获得 Builder 返厂重建:

 1final HttpUrl url;
2  final String method;
3  final Headers headers;
4  final RequestBody body;
5  final Object tag;
6
7  private volatile CacheControl cacheControl; // Lazily initialized.
8
9  Request(Builder builder) {
10    this.url = builder.url;
11    this.method = builder.method;
12    this.headers = builder.headers.build();
13    this.body = builder.body;
14    this.tag = builder.tag != null ? builder.tag : this;
15  }
16
17  public Builder newBuilder() {
18    return new Builder(this);
19  }

Builder 有两个构造,第一个空构造中初始化两个默认值。第二个构造持有 Request 用于重新构建 Builder 返厂重建。

 1public Builder() {
2      this.method = "GET";
3      this.headers = new Headers.Builder();
4    }
5
6    Builder(Request request) {
7      this.url = request.url;
8      this.method = request.method;
9      this.body = request.body;
10      this.tag = request.tag;
11      this.headers = request.headers.newBuilder();
12    }

剩下的就是一些属性初始化的方法,返回值为 Builder 方便链式调用。这里就列出一个方法,详细的请查看源码,最后调用 build() 方法 初始化 Request 传入 Builder 完成构建。

 1  public Builder url(HttpUrl url) {
2      if (url == null) throw new NullPointerException("url == null");
3      this.url = url;
4      return this;
5    }
6...此处省略部分方法
7  public Request build() {
8      if (url == null) throw new IllegalStateException("url == null");
9      return new Request(this);
10    }
2、在 android 源码中 AlertDialog 使用

在 AlertDialog 中使用到的 Builder 模式也是这种套路,我相信如果前面理解了,自己去看看源码应该是手到擒来的事。由于篇幅原因,在这里就不展开了。

结语:个人觉得 对于设计模式的学习是相当有必要的,有时候我们需要去读一下常用开源框架的源码,不仅可以从中学习到一些设计思想,还可以方便日常使用。在一篇博客上面看到这句话 " 我们不重复造轮子不表示我们不需要知道轮子该怎么造及如何更好的造!",而设计模式便是读懂框架源码的基石,因为往往优秀的框架都会涉及很多设计模式。后面本人也会不断更新,不断学习新的设计模式,进而总结出来~

声明:以上仅仅是本人的一点拙见,如有不足之处,还望指出

更多原创文章会在公众号第一时间推送,欢迎扫码关注 张少林同学

张少林同学.jpg

Java Builder 模式,你搞懂了么?的更多相关文章

  1. Java Builder 模式,你搞明白了么?

    Builder 模式定义 Builder 模式中文叫作建造者模式,又叫生成器模式,它属于对象创建型模式,是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.建造者模式是一步一步 ...

  2. Java Builder模式 体验(二)

       在上篇文章中,对Java Builder模式的使用体验主要是从Builder对构造器改造方面的优秀特性来说的,感觉并没有从Java Builder模式本身的功能和作用去写,因此决定再从Build ...

  3. Java设计模式--Java Builder模式

    1.Java Builder模式主要是用一个内部类去实例化一个对象,避免一个类出现过多构造函数,而且构造函数如果出现默认参数的话,很容易出错. public Person(String name) P ...

  4. 每个java初学者都应该搞懂的问题

    对于这个系列里的问题,每个学JAVA的人都应该搞懂.当然,如果只是学JAVA玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列.内容均来自于CSDN的经典老贴. ...

  5. 五分钟学Java:一篇文章搞懂spring和springMVC

    原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者. 本文思维导图 什么是Spring,为什么你要学习spring? 你第一次接触spring框架是在什么时候?相信很多人和我一样,第一次了 ...

  6. JAVA Builder模式构建MAP/LIST的示例

    我们在构建一个MAP时,要不停的调用put,有时候看着觉得很麻烦,刚好,看了下builder模式,觉得这思路不错,于是乎,照着用builder模式写了一个构建MAP的示例,代码如下: import j ...

  7. 线程池 | Java多线程,彻底搞懂线程池

    熟悉Java多线程编程的同学都知道,当我们线程创建过多时,容易引发内存溢出,因此我们就有必要使用线程池的技术了. 最近看了一些相关文章,并亲自研究了一下源码,发现有些文章还是有些问题的,所以我也总结了 ...

  8. Java基础-一文搞懂位运算

    在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+.-.*./.%).关系运算(<.>.<=.>=.==.!=)和逻辑运算(&&.||.!), ...

  9. java Builder模式创建不可变类

    package com.geostar.gfstack.operationcenter.logger.manager.common; /** * Created by Nihaorz on 2017/ ...

随机推荐

  1. Mongodb基于oplog恢复至任意时间

    背景: 最近后端基于mongo的项目越来越多,MySQL基于冷备份+binlog可以恢复至任意时间点,那么mongo是否有同样的功能呢?经过调研发现可以通过dump+oplog可以实现粒度更细致的恢复 ...

  2. Sql_server_2014创建数据库自动备份

    Sql_server_2014创建数据库自动备份 程序员的基础教程:菜鸟程序员

  3. EntityFramework - Code First - 数据迁移

    需求 在更新模型之后同步更新数据库里的表,并不丢失原有数据 使用默认值填充新增加的字段 EntityFramework迁移命令 Enable-Migrations 启用迁移 Add-Migration ...

  4. Java 程序员最喜欢的 11 款免费 IDE 编辑器

    Java开发人员需要花费大量的时间埋头于Java代码中,使用各种不同的IDE(Intergrated Development Environment)来开发Java代码,所以下面我将为大家介绍11个不 ...

  5. [原创]SOUI GDI+渲染引擎下的字体特效,抛砖引玉

    由于SOUI是一种双渲染引擎的DUI库,默认在SKIA渲染引擎下是支持特效字体的,具体请参考DEMO中的源码. 但是使用GDI+渲染时是没有这些特效的,着实比较苦恼,此代抛砖引玉,细节实现 请自己再去 ...

  6. Sprig 面试中 问及 DI,IOC, AOP

    面向切面编程,把散落在程序中的公共部分提取出来,做成切面类,这样的好处在于,代码的可重用,一旦涉及到该功能的需求发生变化,只要修改该代码就行,否则,你要到处修改,如果只要修改1.2处那还可以接受,万一 ...

  7. exp,expdb,imp,impdb的使用

    1.使用expdp要先在数据库中创建directory,并给相应的用户read,write权限. SQL>create dexp和empdp的区别irectory dmpdir as ‘/u01 ...

  8. oracle中的表空间(tablespace)、方案(schema)、段(segment)、区(extent)、块(block)

    数据文件和日志文件是数据库中最重要的文件.它们是数据存储的地方.每个数据库至少有一个与之相关的数据文件,通常情况下不只一个,有很多.数据在数据文件中是如何组织的?要了解这些内容我们首先必须理解什么是表 ...

  9. 使用word文档直接发表博客 8 )

    目前大部分的博客作者在用Word写博客这件事情上都会遇到以下3个痛点: 1.所有博客平台关闭了文档发布接口,用户无法使用Word,Windows Live Writer等工具来发布博客.使用Word写 ...

  10. vmware中安装centos 6.7

    centos 6.7 软件下载地址:http://b.mirrors.lanunion.org/CentOS/6.7/isos/i386/ 引用:http://www.cnblogs.com/sees ...