Builder模式,今天你用了嘛

连载文章

1.前言

在设计模式里,建造者模式你可能听起来有点陌生,但是一提到Builder模式,你可能就会稍微有点印象。这
个印象可能并不是来源于你曾经写过Builder模式。而是在平常编程的时候,总会碰到一个xxx.Builder()类。
这个Builder类就是我们经常在无意中用到的Builder模式,也成为建造者模式。

2.常见的Builder模式

我们总会在无意中用到一些Builder模式,你可能现在想不起来,那么我可以举几个例子稍微提醒一下你!

  1. Retrofit
    (这里面有的参数是我自己封装的类。你只需知道,Retofit的构建是通过,自身的Builder类来构造的就行)
    Retrofit.png
  2. OkHttpClient


    OkHttpClient.png
  3. AlertDialog


    AlertDialog.png

3.Builder模式实例

  1. OkHttpClient源码

这下是不是有了点印象。如果你稍加追究就会发现,无论是Retrofit,还是OkHttpClient亦或是AlertDialog,他们都有一个共同的特点。就是都有一个Builder类。看到这你可能在想,这不是废话嘛。哈哈的确是废话,不过这几个都有一个共同的特点就是,他们的构造方法都不是public修饰的而是protect修饰的。而唯一能够构造返回他们本身对象的就是他们各自Builder类重的build()或者create()方法。而我说的是否正确呢。下面分别上图或者源码证明一下!

/**
 * OkHttpClient源码
 */
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  final int connectTimeout;
  final int readTimeout;
  final int writeTimeout;
  final int pingInterval;

  public OkHttpClient() {
      this(new Builder());
  }

  OkHttpClient(Builder builder) {
    this.connectTimeout = builder.connectTimeout;
    this.readTimeout = builder.readTimeout;
    this.writeTimeout = builder.writeTimeout;
    this.pingInterval = builder.pingInterval;
  }
  //中间省略...
  public static final class Builder {
    int connectTimeout;
    int readTimeout;
    int writeTimeout;
    int pingInterval;

    public Builder() {
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }

    Builder(OkHttpClient okHttpClient) {
      this.connectTimeout = okHttpClient.connectTimeout;
      this.readTimeout = okHttpClient.readTimeout;
      this.writeTimeout = okHttpClient.writeTimeout;
      this.pingInterval = okHttpClient.pingInterval;
    }

    public Builder connectTimeout(long timeout, TimeUnit unit) {
      connectTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }

    public Builder readTimeout(long timeout, TimeUnit unit) {
      readTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }

    public Builder writeTimeout(long timeout, TimeUnit unit) {
      writeTimeout = checkDuration("timeout", timeout, unit);
      return this;
    }

    public Builder pingInterval(long interval, TimeUnit unit) {
      pingInterval = checkDuration("interval", interval, unit);
      return this;
    }

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

这里我把OkHttpClient的源码,缩减了一下,只留下了4个参数。让我们来看一下OkHttp框架是怎么创建实例的。

  • 他的构造方法是public。但是不能设置参数。一旦使用了
OkHttpClient okHttpClient=new OkHttpClient();

那么它内部的参数,都是默认的,无法通过okHttpClient这个实例来设置和修改参数。

  • 我们用OkHttpClient.Builder构建实例
        OkHttpClient.Builder builder=new OkHttpClient.Builder();
        OkHttpClient okHttpClient=builder
                .readTimeout(5*1000, TimeUnit.SECONDS)
                .writeTimeout(5*1000, TimeUnit.SECONDS)
                .connectTimeout(5*1000, TimeUnit.SECONDS)
                .build();

使用Builder创建实例的时候,不但可以链式结构,还可以修改参数(原因是因为,Builder类中的源码,每>个方法的返回值都是Builder本身)

  1. Retrofit源码

这里我们在举一个Retrofit的例子(代码也会稍微简化)

public final class Retrofit {
  private final Map<Method, ServiceMethod<?, ?>> serviceMethodCache = new ConcurrentHashMap<>();

  final okhttp3.Call.Factory callFactory;
  final HttpUrl baseUrl;
  final List<Converter.Factory> converterFactories;
  final List<CallAdapter.Factory> adapterFactories;
  final @Nullable Executor callbackExecutor;
  final boolean validateEagerly;

  Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,
      @Nullable Executor callbackExecutor, boolean validateEagerly) {
    this.callFactory = callFactory;
    this.baseUrl = baseUrl;
    this.converterFactories = unmodifiableList(converterFactories); // Defensive copy at call site.
    this.adapterFactories = unmodifiableList(adapterFactories); // Defensive copy at call site.
    this.callbackExecutor = callbackExecutor;
    this.validateEagerly = validateEagerly;
  }

  public Builder newBuilder() {
    return new Builder(this);
  }

  public static final class Builder {
    private final Platform platform;
    private @Nullable okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    private final List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
    private @Nullable Executor callbackExecutor;
    private boolean validateEagerly;

    Builder(Platform platform) {
      this.platform = platform;
      converterFactories.add(new BuiltInConverters());
    }

    public Builder() {
      this(Platform.get());
    }

    Builder(Retrofit retrofit) {
      platform = Platform.get();
      callFactory = retrofit.callFactory;
      baseUrl = retrofit.baseUrl;
      converterFactories.addAll(retrofit.converterFactories);
      adapterFactories.addAll(retrofit.adapterFactories);
      // Remove the default, platform-aware call adapter added by build().
      adapterFactories.remove(adapterFactories.size() - 1);
      callbackExecutor = retrofit.callbackExecutor;
      validateEagerly = retrofit.validateEagerly;
    }

    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = checkNotNull(factory, "factory == null");
      return this;
    }

    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }

    public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      adapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = checkNotNull(executor, "executor == null");
      return this;
    }

    public Builder validateEagerly(boolean validateEagerly) {
      this.validateEagerly = validateEagerly;
      return this;
    }

    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }
  }
}

上面是Retrofit源码,Retrofit和OkHttp的区别就是,构造方法不是public的,不能直接new出来,只能通过Builder.build();返回一个Retrofit实例。

  1. AlertDialog就不举例了,有兴趣的可以自己看下源码。

4.什么是Builder模式

  1. 定义:将一个复杂对象的构建与表示相分离,使得同样的构建过程可以创建不同的表示。大白话就是,你不需要知道这个类的内部是什么样的,只用把想使用的参数传进去就可以了,达到了解耦的目的。
  2. UML图:下图是一个GOF的传统的Builder模式的图例。由四部分组成1.Director,2.AbstractBuilder(抽象建造者),3.ConcreteBuilder(具体建造者),4.Product(产品类)。其实这里也可以将Product,分为AbstractProduct(抽象产品类),ConcreteProduct(具体产品类)
    builder.png
  3. 使用场景:
    相同的方法不同的执行顺寻,产生不同的事件结果。
    多个部件或零件都可以装配到一个对象中,但是产生的运行结果又不相同时。
    产品类特别复杂,或者产品类中的调用顺序不同产生了不同的作用,这个时候使用Builder设计模式
    初始化一个对象特别复杂,参数多,且很多参数都有默认值。

5.简化:

其实仔细观察之后就会发现。无论时Retrofit还是OkHttp为什么都和传统的Builder模式不一样呢,没有Director类,没有抽象建造者类,也没有具体建造者类。那是因为,在使用过程中,这些库的作者,包括AlertDialog的作者,谷歌的开发人员,都将这三个类简化进了一个Builder类。所以说,我们在使用用设计模式的时候不要太过死板。而应随机应变,连Google的开发人员都是这样的,我们当然也可以取其精华去其糟粕。

/**
 * 作者:jtl
 * 日期:Created in 2019/1/28 11:19
 * 描述:Person类(Builder模式)
 * 更改:
 */
public class Person {
    private String name;
    private int age;

    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Builder newBuilder() {
        return new Builder(this);
    }

    public static class Builder {
        private String name;
        private int age;

        public Builder() {
            this.age = 0;
            this.name = "";
        }

        Builder(Person person) {
            this.name = person.name;
            this.age = person.age;
        }

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

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

        public Person create() {
            return new Person(this);
        }
    }
}

这个是类似与Retrofit和OkHttp的Builder设计模式的一个简化Person类。大家可以仿照这个试一下。

6.区别:

Builder设计模式,之前我们可能没有使用过,但是一些经典的第三方库,却都使用了,将对象的创建与表示相分离,用户不用关心它的内在是什么样的。只需要知道传给他什么参数。在一些有很多参数的产品类中使用该模式,可以避免我们在构造函数中传入大量的默认参数来赋值的尴尬比如:

    private String name;//姓名
    private int age;//年龄
    private String height;//身高
    private String weight;//体重
    private String sex;//性别
    private String address;//家庭住址
    private String nation;//种族
    private String grade;//年纪
    private String clazz;//班级

    public Person(String name, int age, String height, String weight, String sex, String address, String nation, String grade, String clazz) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
        this.sex = sex;
        this.address = address;
        this.nation = nation;
        this.grade = grade;
        this.clazz = clazz;
    }

而使用了Builder模式后,我们不需要关心Person类内部做了什么样的操作。

/**
 * 作者:jtl
 * 日期:Created in 2019/1/28 11:19
 * 描述:简化的Builder模式
 * 更改:
 */

public class Person {
    private String name;//姓名
    private int age;//年龄
    private String height;//身高
    private String weight;//体重
    private String sex;//性别
    private String address;//家庭住址
    private String nation;//种族
    private String grade;//年纪
    private String clazz;//班级

//    public Person(String name, int age, String height, String weight, String sex, String address, String nation, String grade, String clazz) {
//        this.name = name;
//        this.age = age;
//        this.height = height;
//        this.weight = weight;
//        this.sex = sex;
//        this.address = address;
//        this.nation = nation;
//        this.grade = grade;
//        this.clazz = clazz;
//    }

    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.height = builder.height;
        this.weight = builder.weight;
        this.sex = builder.sex;
        this.address = builder.address;
        this.nation = builder.nation;
        this.grade = builder.grade;
        this.clazz = builder.clazz;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Builder newBuilder() {
        return new Builder(this);
    }

    public static class Builder {
        private String name;//姓名
        private int age;//年龄
        private String height;//身高
        private String weight;//体重
        private String sex;//性别
        private String address;//家庭住址
        private String nation;//种族
        private String grade;//年纪
        private String clazz;//班级

        public Builder() {
            this.age = 0;
            this.name = "";
            this.height="150cm";
            this.weight="45kg";
            this.sex="男";
            this.address="";
            this.nation="";
            this.grade="一年级";
            this.clazz="一班";
        }

        Builder(Person person) {
            this.name = person.name;
            this.age = person.age;
            this.height = person.height;
            this.weight = person.weight;
            this.sex = person.sex;
            this.address = person.address;
            this.nation = person.nation;
            this.grade = person.grade;
            this.clazz = person.clazz;
        }

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

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

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

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

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

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

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

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

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

        public Person create() {
            return new Person(this);
        }
    }
}

可能有的小伙伴们就要问了:为什么不直接给Person类当中的参数,一个set方法呢?这样不就不用Builder模式了嘛。关于这个问题,我是这样理解的,如果我们给Person类每一个参数都设置一个set方法,这样会增加Person类的功能和职责。违反了单一原则,不利于后期的维护。

7.结束语:

好了,就说到这儿了。还是那句老话,风里雨里我都在这里等你。你们的关注和点赞是我写作的最大动力。希望大家能够给我一点点的动力。动动您的小手。如果文章中有错误的地方,希望您及时指出,我好改正。让我们共同进步。别忘了关注和点赞。谢谢您了!!!
另附GitHub源码地址:https://github.com/13046434521/DesignPatterns

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