背景
Builder模式是一種設計模式,Android源碼中AlertDialog就是使用Build設計模式,這種模式的主要特點就是鏈式的,方便使用者的調用,使用者無需關心內部如何實現就可以方便調用。
爲什麼要用?
首先了解一下定義:
建造者模式(Builder Pattern):將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。建造者模式是一種對象創建型模式。
使用場景:
-
相同的方法,不同的執行順序,產生不同的事件結果時;
-
多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不相同時;
-
產品類非常複雜,或者產品類中的調用順序不同產生了不同的效能,這個時候使用建造者模式非常合適;
-
當初始化一個對象特別複雜,如參數多,且很多參數都具有默認值時。
UML圖解:
在建造者模式結構圖中包含如下幾個角色:
● Builder(抽象建造者):它爲創建一個產品Product對象的各個部件指定抽象接口,在該接口中一般聲明兩類方法,一類方法是buildPartX(),它們用於創建複雜對象的各個部件;另一類方法是getResult(),它們用於返回複雜對象。Builder既可以是抽象類,也可以是接口。
● ConcreteBuilder(具體建造者):它實現了Builder接口,實現各個部件的具體構造和裝配方法,定義並明確它所創建的複雜對象,也可以提供一個方法返回創建好的複雜產品對象。
● Product(產品角色):它是被構建的複雜對象,包含多個組成部件,具體建造者創建該產品的內部表示並定義它的裝配過程。
● Director(指揮者):指揮者又稱爲導演類,它負責安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,可以在其construct()建造方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端一般只需要與指揮者進行交互,在客戶端確定具體建造者的類型,並實例化具體建造者對象(也可以通過配置文件和反射機制),然後通過指揮者類的構造函數或者Setter方法將該對象傳入指揮者類中。
Builder模式的優缺點
優點:
- 良好的封裝性,使用建造者模式可以使客戶端不必知道產品內部組成的細節
- 建造者獨立,容易擴展
缺點:
- 會產生多餘的Builder對象以及Director對象,消耗內存
變種Builder模式
變種Builder模式在現實Android開發中會經常用到。現實開發中,Director角色會經常被省略。而直接使用一個Builder來進行對象的組裝,這個Builder通常爲鏈式調用,它的關鍵點是每個setter方法都返回自身,即return this
。調用如下:
new TestBuilder().setA("A").setB("B").create()
好處:
目的在於減少對象創建過程中引入的多個重載構造函數、可選參數以及setter過度使用導致不必要的複雜性。
運用實例可以在最下文的參考資料3中查看關於User對象屬性的例子,此處不在贅述。
再舉一例:
參加面試的同學可能會有被問到String
和StringBuffer
和StringBuilder
的區別,這裏就有StringBuilder
這個字符串構建器,我們來簡單看一下這個構建器的用法吧!
String sb = new StringBuilder().append("I ")
.append("am ")
.append("student.").toString();
//我們看一下這裏用的append()函數的源碼
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
可知這個append()
將傳入的str
通過調用父類的方法加到了該對象字符串的後面,並將該對象返回了,這樣就可以連續的調用append()
方法,並且保持對象的唯一性(String
每次都會創建一個新的對象)。
在實際項目的案例:
private const val DEFAULT_CONNECT_TIMEOUT = 5_000L
private const val OTHER_TIME_OUT = 5_000L
/**
* 測試環境網絡請求配置
*/
fun testConfig() {
val httpConfig = HttpConfig.Builder().baseUrl(CommonApi.apiBaseUrl)
// 打印使用http請求日誌
.setLogLevel(HttpLoggingInterceptor.Level.BODY)
// 設置全局超時時間
.connectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT)
.readTimeoutMillis(OTHER_TIME_OUT)
.writeTimeoutMillis(OTHER_TIME_OUT).build()
HttpUtil.initHttpConfig(httpConfig)
}
/**
* 網絡請求配置構建者
*/
class Builder {
private var baseUrl = ""
private var interceptors: ArrayList<Interceptor> = ArrayList()
private var networkInterceptors: ArrayList<Interceptor> = ArrayList()
private var defaultConnectTimeout = 10_000L
private var defaultReadTimeout = 10_000L
private var defaultWriteTimeout = 10_000L
private var retryOnConnectionFailure = false
private var isUseCookie = false
private var isUseCache = false
private var logLevel = HttpLoggingInterceptor.Level.NONE
private val commonHeaders = ArrayMap<String, String>()
private val commonParams = ArrayMap<String, String>()
private var sslParam: SSLParam = HttpsUtil.getSslSocketFactory()
private var hostnameVerifier: HostnameVerifier = HttpsUtil.UnSafeHostnameVerifier
fun baseUrl(url: String): HttpConfig.Builder {
baseUrl = url
return this
}
fun addInterceptor(interceptor: Interceptor): HttpConfig.Builder {
interceptors.add(interceptor)
return this
}
fun addNetworkInterceptor(interceptor: Interceptor): HttpConfig.Builder {
networkInterceptors.add(interceptor)
return this
}
/**
* 連接超時時間
* @param millis 單位是毫秒(默認10秒)
*/
fun connectTimeoutMillis(millis: Long): HttpConfig.Builder {
if (millis <= 0) {
throw IllegalArgumentException("connect timeout must Greater than 0")
}
defaultConnectTimeout = millis
return this
}
/**
* 讀取超時時間
* @param millis 單位是毫秒(默認10秒)
*/
fun readTimeoutMillis(millis: Long): HttpConfig.Builder {
if (millis <= 0) {
throw IllegalArgumentException("read timeout must Greater than 0")
}
defaultReadTimeout = millis
return this
}
/**
* 寫入超時時間
* @param millis 單位是毫秒(默認10秒)
*/
fun writeTimeoutMillis(millis: Long): HttpConfig.Builder {
if (millis <= 0) {
throw IllegalArgumentException("write timeout must Greater than 0")
}
defaultWriteTimeout = millis
return this
}
/**
* 連接失敗時是否重新進行網絡請求
* @param retryOnConnectionFailure 默認爲false
*/
fun retryOnConnectionFailure(retryOnConnectionFailure: Boolean): HttpConfig.Builder {
this.retryOnConnectionFailure = retryOnConnectionFailure
return this
}
/**
* 是否開啓cookie
* @param isUseCookie 默認爲false
*/
fun useCookie(isUseCookie: Boolean): HttpConfig.Builder {
this.isUseCookie = isUseCookie
return this
}
/**
* 是否使用緩存
* @param isUseCache 默認爲false
*/
fun useCache(isUseCache: Boolean): HttpConfig.Builder {
this.isUseCache = isUseCache
return this
}
/**
* 設置日誌級別,參考[HttpLoggingInterceptor.Level]
* @param level 默認爲[HttpLoggingInterceptor.Level.NONE]
*/
fun setLogLevel(level: HttpLoggingInterceptor.Level): HttpConfig.Builder {
logLevel = level
return this
}
/**
* 設置通用請求header
* @param key header鍵
* @param value header值
*/
fun commonHeader(key: String, value: String): HttpConfig.Builder {
commonHeaders[key] = value
return this
}
/**
* 設置通用請求參數
* @param key 參數鍵
* @param value 參數值
*/
fun commonParam(key: String, value: String): HttpConfig.Builder {
commonParams[key] = value
return this
}
/**
* 配置ssl
* @param param ssl參數,默認不對證書做任何檢查
*/
fun sslSocketFactory(param: SSLParam): HttpConfig.Builder {
sslParam = param
return this
}
/**
* 主機名驗證
* @param verifier 默認允許所有主機名
*/
fun hostnameVerifier(verifier: HostnameVerifier): HttpConfig.Builder {
hostnameVerifier = verifier
return this
}
fun build(): HttpConfig {
return HttpConfig(
baseUrl, interceptors, networkInterceptors, defaultConnectTimeout
, defaultReadTimeout, defaultWriteTimeout, retryOnConnectionFailure, isUseCookie
, isUseCache, logLevel, commonHeaders, commonParams, sslParam, hostnameVerifier
)
}
}
private fun showSavePromptDialog() {
MaterialDialog(this)
.message(text ="消息提示標題")
.positiveButton("保存") {
// do something
}
.negativeButton(“取消”) {
// do something
}
.showByCZConfig()
}
/**
*MaterialDialog擴展
*/
fun MaterialDialog.showByCZConfig(): MaterialDialog {
show()
doAsync {
SystemClock.sleep(50L)
uiThread {
val positiveButton = findViewById<AppCompatButton>(R.id.md_button_positive)
val negativeButton = findViewById<AppCompatButton>(R.id.md_button_negative)
val neutralButton = findViewById<AppCompatButton>(R.id.md_button_neutral)
positiveButton.setTextColor(ResourcesUtil.getColor(R.color.common_font_red))
negativeButton.setTextColor(ResourcesUtil.getColor(R.color.common_font_black))
neutralButton.setTextColor(ResourcesUtil.getColor(R.color.common_font_black))
}
}
return this
}
其他:如開源的圖片框架ImageLoader就是通過ImageLoaderConfig進行配置等等。
小結
在開發中,當遇到一個類的構造器或者靜態工廠中具有多個參數,特別是大多數參數是可選的時候,可以考慮使用Builder模式,避免過多的setter方法。Builder模式比較常見的實現形式是通過調用鏈實現,這樣使得代碼更簡潔、易懂。
參考資料: