意圖(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)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Builder | TextConverter | 宣告建立 Product 各部分的抽象介面 |
| ConcreteBuilder | ASCIIConverter、TeXConverter、TextWidgetConverter | 實作 Builder 介面,負責組裝產品各部分;追蹤所建立的表示;提供取回產品的介面(如 GetASCIIText、GetTextWidget) |
| Director | RTFReader | 使用 Builder 介面建構物件 |
| Product | ASCIIText、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 Factory 與 Builder 類似,都能建構複雜物件。主要差異:Builder 著重逐步建構,產品在最後一步才回傳;Abstract Factory 著重產品家族,產品立即回傳
- Builder 經常建構的是 Composite 物件