Java Builder 模式,你搞懂了么?

加油.png

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

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

1、经过构造器构建

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

若是咱们用构造器来构造对象,咱们的作法是 提供第一个包含4个必需属性的构造器,接下来再按可选属性依次重载不一样的构造器,这样是可行的,可是会有如下一些问题:程序员

  • 一旦属性很是多,须要重载n多个构造器,并且各类构造器的组成都是在特定需求的状况下制定的,代码量多了不说,灵活性大大降低
  • 客户端调用构造器的时候,须要传的属性很是多,可能致使调用困难,咱们须要去熟悉每一个特定构造器所提供的属性是什么样的,而参数属性多的状况下,咱们可能由于疏忽而传错顺序。
public class Car {
    /**
     * 必需属性
     */
    private String carBody;//车身
    private String tyre;//轮胎
    private String engine;//发动机
    private String aimingCircle;//方向盘
    /**
     * 可选属性
     */
    private String decoration;//车内装饰品

    /**
     * 必需属性构造器
     *
     * @param carBody
     * @param tyre
     * @param engine
     */
    public Car(String carBody, String tyre, String engine) {
        this.carBody = carBody;
        this.tyre = tyre;
        this.engine = engine;
    }

    /**
     * 假如咱们须要再添加车内装饰品,即在原来构造器基础上再重载一个构造器
     *
     * @param carBody
     * @param tyre
     * @param engine
     * @param aimingCircle
     * @param decoration
     */
    public Car(String carBody, String tyre, String engine, String aimingCircle, String decoration) {
        this.carBody = carBody;
        this.tyre = tyre;
        this.engine = engine;
        this.aimingCircle = aimingCircle;
        this.decoration = decoration;
    }
}

2、JavaBeans模式构建

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

public class Car {
    /**
     * 必需属性
     */
    private String carBody;//车身
    private String tyre;//轮胎
    private String engine;//发动机
    private String aimingCircle;//方向盘
    /**
     * 可选属性
     */
    private String decoration;//车内装饰品

    public void setCarBody(String carBody) {
        this.carBody = carBody;
    }

    public void setTyre(String tyre) {
        this.tyre = tyre;
    }

    public void setEngine(String engine) {
        this.engine = engine;
    }

    public void setAimingCircle(String aimingCircle) {
        this.aimingCircle = aimingCircle;
    }

    public void setDecoration(String decoration) {
        this.decoration = decoration;
    }
}

那么有没有什么方法能够解决以上问题呢?固然有啦~下面咱们的主角上场-----Builder 模式设计模式

3、Builder 模式

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

public final class Car {
    /**
     * 必需属性
     */
    final String carBody;//车身
    final String tyre;//轮胎
    final String engine;//发动机
    final String aimingCircle;//方向盘
    final String safetyBelt;//安全带
    /**
     * 可选属性
     */
    final String decoration;//车内装饰品
    /**
     * car 的构造器 持有 Builder,将builder制造的组件赋值给 car 完成构建
     * @param builder
     */
    public Car(Builder builder) {
        this.carBody = builder.carBody;
        this.tyre = builder.tyre;
        this.engine = builder.engine;
        this.aimingCircle = builder.aimingCircle;
        this.decoration = builder.decoration;
        this.safetyBelt = builder.safetyBelt;
    }
    ...省略一些get方法
    public static final class Builder {
        String carBody;
        String tyre;
        String engine;
        String aimingCircle;
        String decoration;
        String safetyBelt;

        public Builder() {
            this.carBody = "宝马";
            this.tyre = "宝马";
            this.engine = "宝马";
            this.aimingCircle = "宝马";
            this.decoration = "宝马";
        }
         /**
         * 实际属性配置方法
         * @param carBody
         * @return
         */
        public Builder carBody(String carBody) {
            this.carBody = carBody;
            return this;
        }

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

        public Builder aimingCircle(String aimingCircle) {
            this.aimingCircle = aimingCircle;
            return this;
        }

        public Builder decoration(String decoration) {
            this.decoration = decoration;
            return this;
        }
        /**
         * 最后创造出实体car
         * @return
         */
        public Car build() {
            return new Car(this);
        }
    }
}

如今咱们的类就写好了,咱们调用的时候执行一下代码:框架

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

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

car默认构造.png

能够看到,咱们默认的 car 已经制造出来了,默认的零件都是 "宝马",滴滴滴~来不及解释了,快上车。假如咱们不使用默认值,须要本身定制的话,很是简单。只须要拿到 Builder 对象以后,依次调用指定方法,最后再调用 build 返回 car 便可。下面代码示例:函数

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

依旧 debug 看看 car 是否认制成功~ oop

car 定制.png

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

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

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

而后调用的时候:

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

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

依旧 debug 开车走起~
car 属性配置判断.png

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

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

/**
         * 回厂重造
         * @param car
         */
        public Builder(Car car) {
            this.carBody = car.carBody;
            this.safetyBelt = car.safetyBelt;
            this.decoration = car.decoration;
            this.tyre = car.tyre;
            this.aimingCircle = car.aimingCircle;
            this.engine = car.engine;
        }

    /**
     * car 的构造器 持有 Builder,将 builder 制造的组件赋值给 car 完成构建
     *
     * @param builder
     */
    public Car(Builder builder) {
        this.carBody = builder.carBody;
        this.tyre = builder.tyre;
        this.engine = builder.engine;
        this.aimingCircle = builder.aimingCircle;
        this.decoration = builder.decoration;
        this.safetyBelt = builder.safetyBelt;
    }

咦,彷佛有着对称的关系,没错。咱们提供对应的构造。调用返回对应的对象,能够实现返回的效果。在 Car 中添加方法

/**
     * 从新拿回builder 去改造car
     * @return
     */
    public Builder newBuilder() {
        return new Builder(this);
    }

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

Car newCar = car.newBuilder()
                .carBody("奔驰")
                .safetyBelt("奔驰")
                .tyre("奔驰")
                .aimingCircle("奔驰")
                .decoration("奔驰")
                .engine("奔驰")
                .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个以上的话,建议使用 此模式。还有若是类属性存在不肯定性,可能之后还会新增属性时使用,便于扩展。

    4、Builder 模式在 android 中的应用

    1. 在 okHttp 中普遍使用

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

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

    final HttpUrl url;
      final String method;
      final Headers headers;
      final RequestBody body;
      final Object tag;
    
      private volatile CacheControl cacheControl; // Lazily initialized.
    
      Request(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = builder.headers.build();
        this.body = builder.body;
        this.tag = builder.tag != null ? builder.tag : this;
      }
    
      public Builder newBuilder() {
        return new Builder(this);
      }

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

    public Builder() {
          this.method = "GET";
          this.headers = new Headers.Builder();
        }
    
        Builder(Request request) {
          this.url = request.url;
          this.method = request.method;
          this.body = request.body;
          this.tag = request.tag;
          this.headers = request.headers.newBuilder();
        }

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

    public Builder url(HttpUrl url) {
          if (url == null) throw new NullPointerException("url == null");
          this.url = url;
          return this;
        }
    ...此处省略部分方法
      public Request build() {
          if (url == null) throw new IllegalStateException("url == null");
          return new Request(this);
        }
    二、在 android 源码中 AlertDialog 使用

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

    结语:我的以为 对于设计模式的学习是至关有必要的,有时候咱们须要去读一下经常使用开源框架的源码,不只能够从中学习到一些设计思想,还能够方便平常使用。在一篇博客上面看到这句话 " 咱们不重复造轮子不表示咱们不须要知道轮子该怎么造及如何更好的造!",而设计模式即是读懂框架源码的基石,由于每每优秀的框架都会涉及不少设计模式。后面本人也会不断更新,不断学习新的设计模式,进而总结出来~

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

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

    张少林同窗.jpg

    相关文章
    相关标签/搜索