Builder模式,今天你用了嘛
1.前言
在設計模式裡,建造者模式你可能聽起來有點陌生,但是一提到Builder模式,你可能就會稍微有點印象。這
個印象可能並不是來源於你曾經寫過Builder模式。而是在平常程式設計的時候,總會碰到一個 xxx.Builder() 類。
這個Builder類就是我們經常在無意中用到的Builder模式,也成為建造者模式。
2.常見的Builder模式
我們總會在無意中用到一些Builder模式,你可能現在想不起來,那麼我可以舉幾個例子稍微提醒一下你!
- Retrofit
(這裡面有的引數是我自己封裝的類。你只需知道,Retofit的構建是通過,自身的Builder類來構造的就行)
Retrofit.png
-
OkHttpClient
OkHttpClient.png
-
AlertDialog
AlertDialog.png
3.Builder模式例項
- 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本身)
- 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例項。
- AlertDialog就不舉例了,有興趣的可以自己看下原始碼。
4.什麼是Builder模式
- 定義:將一個複雜物件的構建與表示相分離,使得同樣的構建過程可以建立不同的表示。大白話就是,你不需要知道這個類的內部是什麼樣的,只用把想使用的引數傳進去就可以了,達到了解耦的目的。
- UML圖:下圖是一個GOF的傳統的Builder模式的圖例。由四部分組成 1.Director , 2.AbstractBuilder(抽象建造者) , 3.ConcreteBuilder(具體建造者) , 4.Product(產品類) 。其實這裡也可以將 Product ,分為 AbstractProduct(抽象產品類) , ConcreteProduct(具體產品類) 。
builder.png
- 使用場景:
相同的方法不同的執行順尋,產生不同的事件結果。
多個部件或零件都可以裝配到一個物件中,但是產生的執行結果又不相同時。
產品類特別複雜,或者產品類中的呼叫順序不同產生了不同的作用,這個時候使用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 * 描述: * 更改: */ 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; } 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.結束語:
好了,就說到這兒了。還是那句老話,風裡雨裡我都在這裡等你。你們的關注和點贊是我寫作的最大動力。希望大家能夠給我一點點的動力。動動您的小手。如果文章中有錯誤的地方,希望您及時指出,我好改正。讓我們共同進步。別忘了關注和點贊。謝謝您了!!!