建造者模式
建造者模式(Builder Pattern):將對象的創建與表示相分離,使同樣的構建過程產生不一樣的表示,我們可以將這樣的設計模式我們稱之爲建造者模式,也稱構建者模式。一般用於比較複雜的構建對象。
OkHttp中的建造者模式
建造者模式,一般框架中命名規範的都會命名爲XXBuilder
的構造器。在OkHttp中OkHttpClient
中就有該類定義,包含一個內部靜態類Builder
。此外OkHttp中的Request
、Response
、HttpUrl
、Headers
、MultipartBody
等大量使用了類似的建造者模式。由於使用的原因和方式都類似,下面我們從OkHttpClient
來分析一下OkHttp中的建造者模式的使用。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
......
public static final class Builder {
Dispatcher dispatcher;
@Nullable Proxy proxy;
int callTimeout;
int connectTimeout;
......
public Builder dispatcher(Dispatcher dispatcher) {
if (dispatcher == null) throw new IllegalArgumentException("dispatcher == null");
this.dispatcher = dispatcher;
return this;
}
public Builder proxy(@Nullable Proxy proxy) {
this.proxy = proxy;
return this;
}
@IgnoreJRERequirement
public Builder callTimeout(Duration duration) {
callTimeout = checkDuration("timeout", duration.toMillis(), TimeUnit.MILLISECONDS);
return this;
}
public Builder connectTimeout(long timeout, TimeUnit unit) {
connectTimeout = checkDuration("timeout", timeout, unit);
return this;
}
......
public OkHttpClient build() {
return new OkHttpClient(this);
}
}
}
OkHttpClient
靜態內部類Builder
封裝了一系列的可配置參數的設置方法,並返回該對象本身(例如dispatcher()
,proxy()
,callTimeout()
等),方便用戶用鏈式編程技巧調用參數的配置,更利於代碼的閱讀和理解。
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(new LogInterceptor())
....
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.cache(cache)
.build();
如上代碼所示,新建一個OkHttpClient
的構造器Builder
,然後添加一個log攔截器,在設置連接超時,寫超時,讀取超時,緩存,最後調用Builder.build()
方法將Builder
對象傳遞到OkHttpClient
的構造方法裏完成參數的傳遞和對象的創建。
OkHttpClient.Builder
的默認無參構造方法還設置了一系列默認的參數初始值,如果沒有配置的就會使用默認的初始參數,
.....
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
if (proxySelector == null) {
proxySelector = new NullProxySelector();
}
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
callTimeout = 0;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
OkHttpClient.Builder
另外hai 提供了根據原有OkHttpClient
的配置參數的構造函數,代碼如下:
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionSpecs = okHttpClient.connectionSpecs;
this.interceptors.addAll(okHttpClient.interceptors);
this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
......
this.callTimeout = okHttpClient.callTimeout;
this.connectTimeout = okHttpClient.connectTimeout;
this.readTimeout = okHttpClient.readTimeout;
this.writeTimeout = okHttpClient.writeTimeout;
this.pingInterval = okHttpClient.pingInterval;
}
OkHttp爲什麼要使用建造者模式?
我們現通過下面的表來對比使用構造模式的優劣:
優勢 | 劣勢 |
---|---|
對象創建與表示分離,OkHttpClient負責對外提供功能,Builder負責對象的創建,符合類的單一職責 | 類增加 |
對象創建過程使用鏈式編程,集中參數配置,利於閱讀和維護,樣式美觀 | ----- |
封裝了複雜對象的創建過程,使對象對外提供的api更純粹 | ----- |
標準的構造者模式
一般設計模式類相關的書籍介紹的建造者模式,不同於上面我們介紹的OkHttpClient
中介紹的使用方式。其類圖大概如下所示:
一般會有以下四個角色:
產品類(Product):要創建的目標對象,有可能是抽象類,並且有多個子類,也有可能就是直接要創建的產品類。
創建者(Builder):用於創建產品對象的建造器接口,一般是抽象類或者接口,具體產品創建由其子類實現,可以實現多種建造器實現不同的產品創建。有時候如果只需要一個創建者,也可以直接定義爲具體的直接創建者類,比如OkHttp中使用的就是。
直接創建者(ConcreteBuilder):實現了產品各種部件的創建接口的實現類,用於創建產品對象的創建類。
指揮者(Director):也稱爲導演類,指定複雜對象具體的創建順序,根據傳入的創建器創建對象。一般高層調用者根據需要傳入相應的構造器創建所需要的對象。
示例
具體示例我就參考這篇博客:設計模式 | 建造者模式及典型應用
比如工廠流水線組裝電腦,先裝CPU,內存,顯卡,主板…等等。
public class Computer {
private String brand;
private String cpu;
private String mainBoard;
private String hardDisk;
private String displayCard;
private String power;
private String memory;
// 省略 getter, setter, toString
}
public abstract class Builder {
protected Computer computer = new Computer();
public abstract void buildBrand();
public abstract void buildCPU();
public abstract void buildMainBoard();
public abstract void buildHardDisk();
public abstract void buildDisplayCard();
public abstract void buildPower();
public abstract void buildMemory();
public Computer createComputer() {
return computer;
}
}
具體的建造者,LenovoComputerBuilder
,DellComputerBuilder
public class LenovoComputerBuilder extends Builder{
@Override
public void buildBrand() {
computer.setBrand("聯想電腦");
}
@Override
public void buildCPU() {
computer.setCpu("i7 cpu");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("Lenovo主板");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("256GB SSD");
}
@Override
public void buildDisplayCard() {
computer.setDisplayCard("獨立2GB");
}
@Override
public void buildPower() {
computer.setPower("3芯 鋰離子電池 65W AC適配器");
}
@Override
public void buildMemory() {
computer.setMemory("8GB");
}
}
public class DellComputerBuilder extends Builder {
@Override
public void buildBrand() {
computer.setBrand("戴爾電腦");
}
@Override
public void buildCPU() {
computer.setCpu("i5-8300H 四核");
}
@Override
public void buildMainBoard() {
computer.setMainBoard("戴爾主板");
}
@Override
public void buildHardDisk() {
computer.setHardDisk("1T + 128GB SSD");
}
@Override
public void buildDisplayCard() {
computer.setDisplayCard("GTX1060 獨立6GB");
}
@Override
public void buildPower() {
computer.setPower("4芯 鋰離子電池 180W AC適配器");
}
@Override
public void buildMemory() {
computer.setMemory("4G + 4G");
}
}
指揮者 ComputerDirector,指揮構建過程
public class ComputerDirector {
public Computer constructA(Builder builder) {
// 流水線A:逐步構建複雜產品對象
Computer computer;
builder.buildBrand();
builder.buildCPU();
builder.buildDisplayCard();
builder.buildHardDisk();
builder.buildMainBoard();
builder.buildMemory();
builder.buildPower();
computer = builder.createComputer();
return computer;
}
//有時候會出現不同生產線根據資源不同,調整組裝順序
public Computer constructB(Builder builder) {
// 流水線B:逐步構建複雜產品對象
Computer computer;
builder.buildCPU();
builder.buildDisplayCard();
builder.buildHardDisk();
builder.buildMainBoard();
builder.buildMemory();
builder.buildPower();
//最後貼品牌標籤
builder.buildBrand();
computer = builder.createComputer();
return computer;
}
}
客戶端測試
public class Test {
public static void main(String[] args) {
ComputerDirector director = new ComputerDirector();
Builder lenovoBuilder = new LenovoComputerBuilder();
Computer levonoComputer = director.construct(levonosBuilder);
System.out.println(levonoComputer.toString());
Builder dellBuilder = new DellComputerBuilder();
Computer dellComputer = director.construct(dellBuilder);
System.out.println(dellComputer.toString());
}
}
整個過程的簡單類圖大致如下:
和標準的建造者模式類圖很相似,Lenovo品牌和Dell品牌電腦一般都是行業規定的標準配置,所以可以分爲兩個不同的構造器來構建對象,所以分爲兩個單獨的建造器類很合理,也符合類的單一職責。
對比OkHttp中的建造者模式的不同
對比OkHttp中的建造模式,可以發現有些不同:
- OkHttp中只有一個構造器,並沒有子類: OkHttp中
OkHttpClient
只能構造一種對象,區別只在於配置參數的值不同;示例Computer
模擬生產過程中,有了兩種市場標準的不同品牌的兩種不同產品,每種品牌的參數標準基本固定,所以可以分爲兩個不同的構造器,如果不分開不符合類的單一職責。 - OkHttp中構造器爲產品類的靜態內部類 因爲OkHttp中只有唯一的專屬構造器,所以不必單獨列出一個類,也利於其他人理解(專屬構造器),命名也可以簡單命名爲
Builder
,new
的時候調用OkHttpClient.Builder()
一看就知道是OkHttpClient
的專屬構造器。從而不必像示例中需要指明是LvenovoComputerBuilder
或者DellComputerBuilder
。 - OkHttp中沒有指揮者Director 因爲只有唯一的一個構造器,所以有時候我們是可以省略改類型,我們使用的時候也通常會封裝在一個單例類中,並提供一個方法返回構造成功的
OkHttpClient
對象,也相當於提供了一個Director的方法。
我喜歡將類似與OkHttpClient
中的建造者模式稱之爲配置式的建造者模式,因爲建造器的參數都是相當於配置類型的,構造器像是創建了一個配置文件,然後將他傳遞給產品對象,創建對應的產品類對象。還有類似的比如:Retrofit,Glide,AlertDialog等常用的Android框架裏面都使用了類似的建造者模式。
我們通過圖表對比下配置式的構造者模式與標準的構造者模式:
配置式的構造者模式 | 標準的構造者模式 |
---|---|
建造者的參數屬於配置類型的,基本沒有其他作用 | 建造者的參數可能會有其他的邏輯,包括但不限於配置類型 |
建造者具有普適性,參數順序不影響最後對象的生成 | 建造者可能存在多個,參數順序可能會影響最後的對象生成 |
建造器一般只有一個 | 一般具有一個抽象的建造器,多個具體建造器 |
一般不含有指揮者 | 一般會有一個建造者 |
參數一般都具有默認值,可以一個都不配置,直接使用默認值 | 不同構造器的參數默認值基本不同,一般需要指定 |
建造者模式的總結
任何模式都有一定的侷限性和適用範圍,建造者模式也一樣,在適合的環境下才更合理,有時候可能只具備其中某些,比如上文中的 配置式的構建者模式。
優缺點:
建造者模式的主要優點如下,:
- 在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象。
- 每一個具體建造者都相對獨立,而與其他的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象。由於指揮者類針對抽象建造者編程,增加新的具體建造者無須修改原有類庫的代碼,系統擴展方便,符合 “開閉原則”。
- 可以更加精細地控制產品的創建過程。將複雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程。
建造者模式的主要缺點如下:
- 建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用範圍受到一定的限制。
- 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和運行成本。
適用場景:
- 需要生成的產品對象有複雜的內部結構,這些產品對象通常包含多個成員屬性。
- 需要生成的產品對象的屬性相互依賴,需要指定其生成順序。
- 對象的創建過程獨立於創建該對象的類。在建造者模式中通過引入了指揮者類,將創建過程封裝在指揮者類中,而不在建造者類和客戶類中。
- 隔離複雜對象的創建和使用,並使得相同的創建過程可以創建不同的產品。