Builder模式,今天你用了嘛

連載文章

1.前言

在設計模式裏,建造者模式你可能聽起來有點陌生,可是一提到Builder模式,你可能就會稍微有點印象。這 個印象可能並非來源於你曾經寫過Builder模式。而是在日常編程的時候,總會碰到一個 xxx.Builder() 類。 這個Builder類就是咱們常常在無心中用到的Builder模式,也成爲建造者模式。git

2.常見的Builder模式

咱們總會在無心中用到一些Builder模式,你可能如今想不起來,那麼我能夠舉幾個例子稍微提醒一下你!github

  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這個實例來設置和修改參數。bash

  • 咱們用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實例。post

  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的開發人員都是這樣的,咱們固然也能夠取其精華去其糟粕。ui

/**
 * 做者: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類。你們能夠仿照這個試一下。this

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

相關文章
相關標籤/搜索