將一個複雜對象的構建與它的表示分離,使得一樣的構造過程能夠建立不一樣的表示。java
即逐步創建由多個部件組成的對象,每次創建中各部件對外接口一致,但內部實現功能能夠不同,相同的構建過程能夠建立不一樣的對象。android
適用於流程固定(順序不必定固定),但建造的目標不一樣的場景。例如購買電腦,不一樣人對電腦有不一樣的需求,辦公向的,遊戲向的。但都是由機箱、主板、顯卡、電源等組成。又如建造房子,不一樣房子結構不一樣,但都有地基、牆、地板、門窗等等。又如軟件開發,產品經理提出功能需求,技術領導拆分任務,指定具體程序員完成。程序員
適用於構建許多字段和嵌套對象組合成的複雜對象。安全
例如,假設要建造上圖中的 House
。建簡單的房子,只須要建造牆和地板,安裝門窗,並建造一個屋頂。 可是,若是你想要建造一個更大,更明亮的房子,後院和其餘設施(如加熱系統,管道和電線)怎麼辦?app
最簡單的解決方案是擴展基類 House
並建立一組子類來覆蓋參數的全部組合。但它會產生不少的子類。 任何新參數(例如門廊樣式)都須要增長新的子類。ide
還有另外一種方法不涉及子類。在基類 House
中建立一個巨型構造函數,其中包含控制 house
對象的全部可能參數。這種方法雖然確實消除了對子類的需求,但它產生了另外一個問題。函數
那就是產生了不少未使用的參數,令構造函數變得很難看。如不是全部房子都有雕像和泳池。ui
建造者模式將對象的構造過程拆分,並由專門的 Builder
對象來構造對象。this
該模式將對象構造過程拆分紅一組步驟(buildWalls,buildDoor等)。要建立對象,須要在 Builder
對象中執行相應的步驟,並只調用須要的步驟便可,不須要調用全部步驟。spa
當須要構建產品的各類表示時,某些建造步驟可能須要不一樣的實現。 例如,小屋的牆壁能夠用木頭建造,但城堡的牆壁必須用石頭建造。
在這種狀況下,能夠建立多個不一樣的 Builder
實例類,這些 Builder
以不一樣的方式實現同一組構建步驟。 而後,能夠在構造過程當中使用這些 Builder
來生成不一樣類型的對象。
例如,假設一個 Builder
,用木頭和玻璃建造一切,第二個用石頭和鐵建造一切,第三個用金和鑽石製造一切。 經過調用相同的步驟,能夠得到第一個建築師的常規房屋,第二個建築物的小城堡和第三個建築物的宮殿。
爲了更好的構建複雜對象,也能夠建立 Director
類,將一系列建造步驟封裝到其中,經過調用它來按順序執行一系列建造步驟。它並非必須的,可是將經常使用的建造步驟封裝能夠更好在其餘地方複用。
角色 | 類別 | 說明 |
---|---|---|
Builder | 抽象的建造者 | 接口或抽象類,將建造的具體過程交由其子類實現,便於擴展。但全部建造步驟和返回產品函數均在此聲明。 |
ConcreteBuilder | 具體的建造者 | 能夠有多個,實現 Builder 中的全部建造步驟。不一樣建造者的實現方式能夠不一樣。 |
Product | 具體的產品類 | 要被建造的較爲複雜的對象。 |
Director | 導演者 | 定義調用建造步驟的順序。通常不與產品類發生依賴關係,與建造者類直接交互,一般封裝可能被常用的一系列建造步驟。 |
在整個過程當中:
如上圖所示,設置選項由不少個部分組成,但每次實際使用時,不須要同時啓用全部部分,而是根據使用狀況不一樣,組合不一樣部分,同時一些部分還有前後順序,如點擊事件需先添加了右箭頭才能添加。此類複雜對象可使用建造者模式建立(此處僅用於演示建造者模式。 android 中實際使用時,爲其添加自定義 attr 屬性,在 xml 中直接設置更方便,而且實際應用建造模式時並不須要嚴格建立四個部分,除 Product
外其他三個部分常常混合使用)。
Product — SettingItemView
class SettingItemView(context: Context) : LinearLayout(context) {
// ... 省略構建函數
fun addTitle(title: String) {
settingTitleView.visibility = View.VISIBLE
settingTitleView.text = title
}
fun addRightArrow(isShow: Boolean) {
settingRightArrow.visibility = View.VISIBLE
settingRightArrow.visibility = if (isShow) View.VISIBLE else View.GONE
}
fun addOnClickListener(listener: View.OnClickListener) {
if (if (settingRightArrow.visibility != View.VISIBLE) {
throw IllegalStateException("add click listener should after adding right arrow")
}
setOnClickListener(listener)
}
// ... 省略設置函數
}
複製代碼
Builder
interface Builder {
fun reset()
fun addTitle(title: String)
fun addRightArrow(isShow: Boolean)
fun addOnClickListener(listener: View.OnClickListener)
fun create(): SettingItemView
}
複製代碼
ConcreteBuilder
class ConcreteBuilder(private val context: Context) : Builder {
private var settingItemView = SettingItemView(context)
override fun reset() {
settingItemView = SettingItemView(context)
}
override fun addTitle(title: String) {
settingItemView.addTitle(title)
}
override fun addRightArrow(isShow: Boolean) {
settingItemView.addRightArrow(isShow)
}
override fun addOnClickListener(listener: View.OnClickListener) {
settingItemView.addOnClickListener(listener)
}
override fun create(): SettingItemView {
return settingItemView
}
}
複製代碼
Builder 構建時另外一種經常使用方式是將全部參數記錄下來,在建立時再統一構建並檢查錯誤。
Director
class Director(private val builder: Builder) {
fun construct() {
builder.addTitle("帳號與安全")
builder.addRightArrow(true)
builder.addOnClickListener(View.OnClickListener {
Toast.makeText(it.context, "點擊", Toast.LENGTH_SHORT).show()
})
}
}
複製代碼
Director 一般只負責調用 Buidler 執行操做,不直接返回產品。
調用
val builder = ConcreteBuilder(context)
val director = Director(builder)
val settingItemView = builder.create()
複製代碼
將生成的 SettingItemView 對象加載到窗口中去後以下圖所示:
在實際應用中,採用建造模式構建複雜對象時,一般會對建造模式進行必定簡化,大體簡化以下:
public class AlertDialog extends Dialog implements DialogInterface {
// ... 省略若干構造方法
protected AlertDialog(Context context, @StyleRes int themeResId) {
this(context, themeResId, true);
}
// ... 省略若干設置屬性方法
public void setView(View view) {
mAlert.setView(view);
}
// 建造者
public static class Builder {
// ... 省略
public Builder setTitle(CharSequence title) {
P.mTitle = title;
return this;
}
// 建造者中提供建立目標對象的方法
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
}
}
複製代碼
使用建造模式:
val builder = AlertDialog.Builder(context)
builder.setTitle("問題:")
.setMessage("請問你滿十八歲了嗎?")
.setIcon(R.mipmap.ic_launcher_round)
.setCancelable(true)
val dialog = builder.create()
複製代碼
AlertDialog 的建造者模式 AlertDialog.Builder
同時扮演了 Builder
、ConcreateBuilder
、Director
三個角色。
都屬於建立模式
意圖不一樣
工廠模式關注的是對象總體,不關注對象的組成部分,建造者模式關注對象組成部分的建立過程。
粒度不一樣
工廠模式建立的產品性質相對單一,建造者模式建立的是複合產品,由複雜部分組成,部分不一樣構成的產品也不一樣。
Article by Wuhb