建造者模式(Builder Pattern)

什麼是建造者模式

定義

將一個複雜對象的構建與它的表示分離,使得一樣的構造過程能夠建立不一樣的表示。java

即逐步創建由多個部件組成的對象,每次創建中各部件對外接口一致,但內部實現功能能夠不同,相同的構建過程能夠建立不一樣的對象。android

特色

適用於流程固定(順序不必定固定),但建造的目標不一樣的場景。例如購買電腦,不一樣人對電腦有不一樣的需求,辦公向的,遊戲向的。但都是由機箱、主板、顯卡、電源等組成。又如建造房子,不一樣房子結構不一樣,但都有地基、牆、地板、門窗等等。又如軟件開發,產品經理提出功能需求,技術領導拆分任務,指定具體程序員完成。程序員

適用範例

適用於構建許多字段和嵌套對象組合成的複雜對象。安全

Lots of subclasses create another problem

例如,假設要建造上圖中的 House 。建簡單的房子,只須要建造牆和地板,安裝門窗,並建造一個屋頂。 可是,若是你想要建造一個更大,更明亮的房子,後院和其餘設施(如加熱系統,管道和電線)怎麼辦?app

最簡單的解決方案是擴展基類 House 並建立一組子類來覆蓋參數的全部組合。但它會產生不少的子類。 任何新參數(例如門廊樣式)都須要增長新的子類。ide

還有另外一種方法不涉及子類。在基類 House 中建立一個巨型構造函數,其中包含控制 house 對象的全部可能參數。這種方法雖然確實消除了對子類的需求,但它產生了另外一個問題。函數

The telescopic constructor

那就是產生了不少未使用的參數,令構造函數變得很難看。如不是全部房子都有雕像和泳池。ui

建造者模式將對象的構造過程拆分,並由專門的 Builder 對象來構造對象。this

Applying the Builder pattern

該模式將對象構造過程拆分紅一組步驟(buildWalls,buildDoor等)。要建立對象,須要在 Builder 對象中執行相應的步驟,並只調用須要的步驟便可,不須要調用全部步驟。spa

當須要構建產品的各類表示時,某些建造步驟可能須要不一樣的實現。 例如,小屋的牆壁能夠用木頭建造,但城堡的牆壁必須用石頭建造。

在這種狀況下,能夠建立多個不一樣的 Builder 實例類,這些 Builder 以不一樣的方式實現同一組構建步驟。 而後,能夠在構造過程當中使用這些 Builder 來生成不一樣類型的對象。

img

例如,假設一個 Builder ,用木頭和玻璃建造一切,第二個用石頭和鐵建造一切,第三個用金和鑽石製造一切。 經過調用相同的步驟,能夠得到第一個建築師的常規房屋,第二個建築物的小城堡和第三個建築物的宮殿。

爲了更好的構建複雜對象,也能夠建立 Director 類,將一系列建造步驟封裝到其中,經過調用它來按順序執行一系列建造步驟。它並非必須的,可是將經常使用的建造步驟封裝能夠更好在其餘地方複用。

結構

角色 類別 說明
Builder 抽象的建造者 接口或抽象類,將建造的具體過程交由其子類實現,便於擴展。但全部建造步驟和返回產品函數均在此聲明。
ConcreteBuilder 具體的建造者 能夠有多個,實現 Builder 中的全部建造步驟。不一樣建造者的實現方式能夠不一樣。
Product 具體的產品類 要被建造的較爲複雜的對象。
Director 導演者 定義調用建造步驟的順序。通常不與產品類發生依賴關係,與建造者類直接交互,一般封裝可能被常用的一系列建造步驟。

UML圖

Structure of the Builder design pattern

在整個過程當中:

  1. 用戶不需瞭解建造過程和細節
  2. 用戶只需給出複雜對象的的類型便可建立對象
  3. 建造者按流程一步步建立出複雜對象

示例

完整版 - SettingItemView

settingItemViewComponent

如上圖所示,設置選項由不少個部分組成,但每次實際使用時,不須要同時啓用全部部分,而是根據使用狀況不一樣,組合不一樣部分,同時一些部分還有前後順序,如點擊事件需先添加了右箭頭才能添加。此類複雜對象可使用建造者模式建立(此處僅用於演示建造者模式。 android 中實際使用時,爲其添加自定義 attr 屬性,在 xml 中直接設置更方便,而且實際應用建造模式時並不須要嚴格建立四個部分,除 Product 外其他三個部分常常混合使用)。

  1. 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)
        }
      	// ... 省略設置函數
    }
    複製代碼
  2. Builder

    interface Builder {
      
        fun reset()
    
        fun addTitle(title: String)
    
        fun addRightArrow(isShow: Boolean)
    
        fun addOnClickListener(listener: View.OnClickListener)
    
        fun create(): SettingItemView
    }
    複製代碼
  3. 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 構建時另外一種經常使用方式是將全部參數記錄下來,在建立時再統一構建並檢查錯誤。

  4. 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 執行操做,不直接返回產品。

  5. 調用

    val builder = ConcreteBuilder(context)
    val director = Director(builder)
    val settingItemView = builder.create()
    複製代碼

將生成的 SettingItemView 對象加載到窗口中去後以下圖所示:

settingItemView.png

簡化版 - AlertDialog

在實際應用中,採用建造模式構建複雜對象時,一般會對建造模式進行必定簡化,大體簡化以下:

  1. 直接使用建造者模式建立對象,因此不用再定義一個抽象建造類接口,直接提供一個具體的建造類便可。
  2. 建立一個複雜對象時,可能有不少種不一樣的選擇和步驟,能夠去掉導演者,把導演者的功能和客戶的功能結合起來,此時客戶就至關於導演者,它來指導建造器去構建須要的複雜對象。
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 同時扮演了 BuilderConcreateBuilderDirector 三個角色。

優缺點

優勢

  1. 建立產品的步驟和產品自己分離,相同中的建立過程能夠建立出不一樣的產品。
  2. 容許對象經過多個步驟建立,能夠改變過程。
  3. 向客戶隱藏具體實現。
  4. 建造者相對獨立,能夠方便的替換或增長新的。

缺點

  1. 產品必須有共同點,範圍有限制。
  2. 若是產品內部發生改變,多個建造者都須要修改,成本大。
  3. 採用生成器模式建立對象的客戶,須要具有更多的領域知識。

使用場景

  1. 對象具備複雜的內部結構
  2. 想將複雜對象的建立和使用分離
  3. 要生產的對象屬性相互依賴,須要指定生成順序
  4. 一些基本部分不會變,而其組合常常變化的時候

建造者模式 VS 工廠模式

類似

都屬於建立模式

不一樣

  1. 意圖不一樣

    工廠模式關注的是對象總體,不關注對象的組成部分,建造者模式關注對象組成部分的建立過程。

  2. 粒度不一樣

    工廠模式建立的產品性質相對單一,建造者模式建立的是複合產品,由複雜部分組成,部分不一樣構成的產品也不一樣。

Article by Wuhb

相關文章
相關標籤/搜索