# 虎年行大運 - Design Pattern - Builder ## 前言 工程師或多或少都曾經遇過因應服務的擴展,必須要增加輸入參數的情況。 今天接到要做一杯紅茶的需求? 好的沒問題! 建一個方法,只需要輸入茶包、冰塊、甜度三種參數,這樣不僅紅茶,連其他茶飲都可以一併完成。 過幾天接到飲品要有額外添加料的需求? 也沒問題! 在上述方法中增加一個新的參數,這參數會帶入要增加那些添加料的資訊,需求完成。 又過了幾天,飲品要分成大、中、小杯裝...? 飲品要分成店家提供的杯子或顧客自行攜帶的杯子...? 飲品要做成熱飲...? 飲品要出特定季節版本或活動版本...? 飲品要根據店別出特定店版本...? 太多太多的需求慢慢加進來,才會發現...你的參數是不是越來越多了? 從一行變兩行、變三行。如果這個方法來自於介面的話,那代表在更動上會動到更多更多的實做類別。 累不累? 麻不麻煩? 之後老闆說,準備開一間咖啡店,飲品作法與原先的飲料店不同,但至些需求參數相差無幾,頭開始痛了嗎? 你需要的是可以幫你在一個實作類別中抽離建構子(Constructor)與參數的東西。 就是今天要介紹的,在解綁建構子與輸入參數的設計模式 -- **Builder** ## 主題 **Builder Pattern** 中文常見名稱 **配接器模式**、**轉接器模式** 屬於 Creational Pattern 的一種,目的是為了將建構子與過多的輸入參數解綁,讓建構子單純是建構子,參數的輸入可以根據流程進行動態的處理,而不是所有使用該方法的人都必須要求所有輸入參數的部分。 這樣就算今天需要建立許多的實作方法,每一個實作的方法與參數還是可以分開處理。你可以更彈性化的去加入其他設計模式來幫助完成。 **優點:** - 解偶建構子與參數 - 動態參數處理 **缺點:** - 只適用於相似性高的產出 - 若內部使用 Builder 過多則會造成模組數量容易肥大 ![](https://i.imgur.com/20W6cku.png) *[該圖引用來自 Wiki Builder Pattern - Class Diagram](https://en.wikipedia.org/wiki/Builder_pattern)* **架構圖物件說明** | 項目 | 說明 | | ---- | ---- | | Product | 產品 | | Builder | 介面 | | ConcreteBuilder | 實作 Builder 介面的類別 | | Director | 負責設計 ConcreteBuilder 該如何執行 | ## 實行方式 ```java= /** * @author kaichen * Builder 模式,把建構用的參數與建構子解耦,讓參數可以被特定的方法處理,並增加 Director 作為使用前的準備層,在這一層將參數依照特定邏輯放入 Builder 中 */ public class Builder { public static void main(String [] args){ InitBuilder initBuilder = new InitBuilderImpl(); Director cb1 = new Director_1(initBuilder); System.out.println(cb1.getResult()); Director cb2 = new Director_2(initBuilder); System.out.println(cb2.getResult()); } } interface InitBuilder{ public void setElement_1(String str); public void setElement_2(String str); public void setElement_3(String str); public void build(); public String getResult(); } class InitBuilderImpl implements InitBuilder{ private String element_1; private String element_2; private String element_3; private String result; public InitBuilderImpl(){} @Override public void build() { result = new StringBuilder().append("E1: ").append(element_1).append(", ") .append("E2: ").append(element_2).append(", ") .append("E3: ").append(element_3).toString(); } @Override public String getResult() { return result; } @Override public void setElement_1(String str) { this.element_1 = str; } @Override public void setElement_2(String str) { this.element_2 = str; } @Override public void setElement_3(String str) { this.element_3 = str; } } interface Director { public void setElements(); public String getResult(); } class Director_1 implements Director{ private final InitBuilder builder; private final String element_1 = "Builder 1 Element 1"; private final String element_2 = "Builder 1 Element 2"; private final String element_3 = "Builder 1 Element 3"; public Director_1(InitBuilder builder){ this.builder = builder; setElements(); this.builder.build(); } @Override public void setElements(){ this.builder.setElement_1(element_1); this.builder.setElement_2(element_2); this.builder.setElement_3(element_3); } @Override public String getResult(){ return this.builder.getResult(); } } class Director_2 implements Director{ private final InitBuilder builder; private final String element_1 = "Builder 2 Element 1"; private final String element_2 = "Builder 2 Element 2"; private final String element_3 = "Builder 2 Element 3"; public Director_2(InitBuilder builder){ this.builder = builder; setElements(); this.builder.build(); } @Override public void setElements(){ this.builder.setElement_1(element_1); this.builder.setElement_2(element_2); this.builder.setElement_3(element_3); } @Override public String getResult(){ return this.builder.getResult(); } } ``` 而有時候為求處理上的方便,會把輸入參數的方法改寫為回傳本身,這樣就可以在一行內處理完。如以下: ```java= interface InitBuilder{ public InitBuilder setElement_1(String str); public InitBuilder setElement_2(String str); public InitBuilder setElement_3(String str); public void build(); public String getResult(); } class InitBuilderImpl implements InitBuilder{ ... @Override public InitBuilder setElement_1(String str) { this.element_1 = str; return this; } @Override public InitBuilder setElement_2(String str) { this.element_2 = str; return this; } @Override public InitBuilder setElement_3(String str) { this.element_3 = str; return this; } ... } class Director_1 implements Director{ ... @Override public void setElements(){ this.builder.setElement_1(element_1) .setElement_2(element_2) .setElement_3(element_3); } ... } ``` 範例中建立介面 **InitBuilder**、實作類 **InitBuilderImpl**,藉由不同的 **Director** (**Director_1**、**Director_2**) 處理輸入參數、執行,取得不同的結果。 總結: - Builder: 介面 - BuilderImpl: 不同的流程、資料處理邏輯 (面向系統) - Director: 介面 - DirectorImpl: 不同的業務處理邏輯,內容會呼叫 BuilderImpl 協助完成全部或部分內容 (面向業務) :::danger 用一句話介紹 Builder Pattern: **解偶建構子或參數的設計模式** ::: 首頁 [Kai 個人技術 Hackmd](/2G-RoB0QTrKzkftH2uLueA) ###### tags: `Design Pattern`