意圖(Intent)#

將一個複雜物件的建構過程與其表示分離,使相同的建構流程能產生不同的表示。

動機(Motivation)#

考慮一個 RTF 文件閱讀器,需要將 RTF 轉換成多種格式:純文字 ASCII、可編輯的 text widget、或 TeX 格式。由於可能的轉換格式是開放性的,應該要能輕鬆新增轉換格式而不修改閱讀器本身。

解法是將 RTFReader 配置一個 TextConverter 物件。RTFReader 解析 RTF 文件時,每遇到一個 token 就呼叫 TextConverter 的對應操作來執行轉換。不同的 TextConverter 子類別(ASCIIConverter、TeXConverter、TextWidgetConverter)各自負責不同格式的輸出。

在此模式中,converter 稱為 Builder,reader 稱為 Director。Builder 封裝了複雜物件的組裝細節,Director 負責驅動建構流程。兩者分離後,同一個解析演算法可搭配不同的 Builder 來產出不同的結果。

Builder 模式的精髓在於:將「怎麼建構」(Director 的演算法)與「建構出什麼」(Builder 的產品表示)解耦,實現建構流程的重用。

適用場景(Applicability)#

  • 建立複雜物件的演算法應獨立於組成物件的零件及其組裝方式
  • 建構過程必須允許被建構物件有不同的表示

結構(Structure)#

Director 使用 Builder 介面驅動建構流程。ConcreteBuilder 實作 Builder 介面,逐步組裝並追蹤所建立的 Product。Client 建立 Director 並配置所需的 Builder,最後從 Builder 取回成品。

classDiagram
    class Director {
        +Construct()
    }
    class Builder {
        <<interface>>
        +BuildPartA()
        +BuildPartB()
        +GetResult()
    }
    class ConcreteBuilder {
        +BuildPartA()
        +BuildPartB()
        +GetResult()
    }
    class Product
    Director o--> Builder
    Builder <|.. ConcreteBuilder
    ConcreteBuilder ..> Product : creates

參與者(Participants)#

參與者範例職責
BuilderTextConverter宣告建立 Product 各部分的抽象介面
ConcreteBuilderASCIIConverter、TeXConverter、TextWidgetConverter實作 Builder 介面,負責組裝產品各部分;追蹤所建立的表示;提供取回產品的介面(如 GetASCIIText、GetTextWidget)
DirectorRTFReader使用 Builder 介面建構物件
ProductASCIIText、TeXText、TextWidget被建構的複雜物件

協作方式(Collaborations)#

  • Client 建立 Director 並配置所需的 Builder
  • Director 在需要建構產品各部分時通知 Builder
  • Builder 處理 Director 的請求,逐步將零件加入產品
  • Client 從 Builder 取回最終產品
sequenceDiagram
    participant Client
    participant Director
    participant ConcreteBuilder
    Client->>ConcreteBuilder: new ConcreteBuilder()
    Client->>Director: new Director(builder)
    Client->>Director: Construct()
    Director->>ConcreteBuilder: BuildPartA()
    Director->>ConcreteBuilder: BuildPartB()
    Director->>ConcreteBuilder: BuildPartC()
    Client->>ConcreteBuilder: GetResult()
    ConcreteBuilder-->>Client: Product

優缺點(Consequences)#

  • 可替換產品的內部表示 —— Builder 提供抽象介面,隱藏產品的內部結構與組裝方式。更換 Builder 就能改變產品的內部表示
  • 建構與表示分離 —— 建構邏輯與產品表示的程式碼各自封裝。不同的 Director 可重用同一批 Builder 來建構同類產品的不同變體
  • 更精細的建構控制 —— 不同於一次性建立產品的模式,Builder 讓 Director 逐步控制建構過程,直到產品完成才取回。這提供了對最終產品內部結構的精細控制

Builder 的基底類別方法預設為空操作(而非純虛擬函式),這樣 ConcreteBuilder 只需覆寫感興趣的操作,簡化實作。

實作要點(Implementation)#

  • 組裝與建構介面 —— Builder 介面必須夠通用,以支援所有 ConcreteBuilder 的需求。通常採用「追加」模型——每次建構請求的結果附加到先前的產品上。但有些情況需要存取先前建構的部分(如在既有房間之間加門)
  • 產品通常沒有抽象基底類別 —— 各 ConcreteBuilder 的產品差異通常很大(如 ASCIIText 與 TextWidget),不需要共同的父類別。Client 通常知道使用哪個 ConcreteBuilder,因此能直接處理對應的產品
  • 預設空方法 —— Builder 的建構方法定義為空方法而非抽象方法,讓 ConcreteBuilder 只覆寫需要的操作

已知應用(Known Uses)#

  • ET++ 的 RTF 轉換器應用程式,使用 Builder 處理 RTF 格式的文字
  • Smalltalk-80 的 Parser 類別作為 Director,以 ProgramNodeBuilder 逐步建構 parse tree
  • Smalltalk-80 的 ByteCodeStream 作為 Builder,將編譯結果建構為 byte array
  • Adaptive Communications Environment 的 Service Configurator 框架使用 Builder 建構網路服務元件

相關模式(Related Patterns)#

  • Abstract FactoryBuilder 類似,都能建構複雜物件。主要差異:Builder 著重逐步建構,產品在最後一步才回傳;Abstract Factory 著重產品家族,產品立即回傳
  • Builder 經常建構的是 Composite 物件