1. 程式人生 > >建造者模式——結合案例,通俗易懂

建造者模式——結合案例,通俗易懂

> 一個設計模式解決一類問題,最近學習了一下建造者模式,看了很多部落格,講的模稜兩可,所以決定寫一下我覺得比較好理解的簡介 > 參考自知乎 https://zhuanlan.zhihu.com/p/58093669, ## 一、介紹 ##### 1、啥是建造者模式 是將一個複雜的物件的**構建**與它的**表示**分離,使得同樣的構建過程可以建立不同的表示 看不懂對吧!其實我也看不懂,不管它,通過案例和程式碼加深理解 ##### 2、使用場景 一個設計模式解決一類問題,那麼建造者模式解決了什麼問題呢?——**物件的構建過於複雜的問題** * 當一個類的建構函式引數過多(超過四個),並且有的引數可有可無,或者很多產品有預設值。 * 產品類非常複雜或者產品類因為呼叫順序不同而產生不同作用 ##### 3、優點 * 複雜產品的建立步驟分解在不同的方法中,這些方法可以呼叫順序不同,結果不同,建立結果很清晰 ##### 4、缺點 * 如果產品的內部變化複雜,可能會導致需要定義很多具體建造者來實現這種變化。
## 二、案例 理論總是難以理解的,現在通過案例分析問題,一步步瞭解使用建造者模式的好處 【案例】好好看一下這個案例 > KFC套餐 > 假如目前KFC裡面有很多個套餐 > 在套餐裡面有必點,也有選點,然後每個單品又有大小之分 > 必點:漢堡(hamburger),薯條(chips) > 選點:雞腿(chicken),可樂(cola),披薩(pizza) 【用Java程式碼模擬場景】 我們如何構成這麼多套餐例項呢? 我們不使用建造者模式也能構建程式碼,但是建造者模式會讓程式碼看上去更裝逼,程式碼到後期更結構化更容易維護和拓展 首先構建這個實體類`KFC` ```java public class KFC { //套餐必點 private String hamburger; private String chips; //套餐選點 private String chicken; private String cola; private String pizza; } ``` 我們的想法是不是摺疊建構函式來建立例項,下面來嘗試一下 ```java public class KFC{ //省略了上面的屬性..... //必點套餐A public KFC(String hamburger,String chips){ this(hamburger,chips,null,null,null); } A //套餐B public KFC(String hamburger,String chips,String chicken){ this(hamburger,chips,chicken,null,null); } //套餐C public KFC(String hamburger,String chips,String chicken,String cola){ this(hamburger,chips,chicken,cola,null); } //......還有好多種組合方式,你會發現使用摺疊建構函式的方法十分複雜 //全選 public KFC(String hamburger,String chips,String chicken,String cola,String pizza){ this.hamburger = hamburger; this.chips = chips; this.chicken = chicken; this.cola = cola; this.pizza = pizza; } } ``` 我們會發現使用摺疊建構函式的方式很複雜,很噁心,程式碼看都不想看 那麼有人會想,我可以使用`set`方法來建立,我只要一個必點構造就好了,那繼續模擬咯 ```java public class KFC{ //.....省略了屬性 //必點 public KFC(String hamburger,String chips){ this.hamburger = hamburger; this.chips = chips; } //set方法 public void setChicken(String chicken) { this.chicken = chicken; } public void setCola(String cola) { this.cola = cola; } public void setPizza(String pizza) { this.pizza = pizza; } //例項化物件,你會發現這種方式就友好很多 public static void main(String[] args) { KFC kfc = new KFC("大漢堡","大薯條"); //加小份可樂 kfc.setCola("小可樂"); //加個雞腿 kfc.setChicken("大雞腿"); System.out.println(kfc); } } ``` 你會發現使用`set`方式就友好了很多 這個雖然友好了很多,但是也有點小毛病,就是你**set太隨意了,我可能這個套餐裡面沒有這個單品,而使用set的人卻不知道**,造成錯誤的套餐出現!。 為了解決上面的兩種問題:一種設計模式解決一類問題,所以**建造者模式**就出現了
## 三、建造者模式 先了解一下又哪些角色吧,看不懂沒關係,看一下程式碼就懂了 ![](https://img2020.cnblogs.com/blog/1988391/202005/1988391-20200503135924734-1371940741.png) 四個角色 **Product(產品角色):** 一個具體的產品物件。 **Builder(抽象建造者):** 建立一個Product物件的各個部件指定的抽象介面。 **ConcreteBuilder(具體建造者):** 實現抽象介面,構建和裝配各個部件。 **Director(指揮者):** 構建一個使用Builder介面的物件。它主要是用於建立一個複雜的物件。它主要有兩個作用,一是:隔離了客戶與物件的生產過程,二是:負責控制產品物件的生產過程。 > 拿其中兩個套餐舉例 > > 套餐A:漢堡,薯條,大雞腿 > 套餐B:漢堡,薯條,小雞腿,小可樂,小披薩 > > 其中薯條和漢堡可大可小,並且必須有, > 其它的都為固定大小,但是你可以選擇有或沒有 * 產品(KFC) ```java public class KFC { //套餐必點 private String hamburger; private String chips; //套餐選點 private String chicken; private String cola; private String pizza; //必點 public KFC(String hamburger,String chips){ this.hamburger = hamburger; this.chips = chips; } //set方法 public void setChicken(String chicken) { this.chicken = chicken; } public void setCola(String cola) { this.cola = cola; } public void setPizza(String pizza) { this.pizza = pizza; } } ``` * Builder 定義一個介面,表明需要建造什麼,得到什麼 ```java public interface Builder { void setChicken(); void setCola(); void setPizza(); KFC getKFC(); } ``` * ConcreteBuilder: 此時應該注意,這個時候還沒有生產套餐,只是定義套餐 套餐A ```java public class ConcreteBuilder1 implements Builder { private KFC kfc; //這一步非常重要 public ConcreteBuilder1(String hamburger,String chips){ kfc = new KFC(hamburger,chips); } @Override public void setChicken() { kfc.setChicken("大雞腿"); } @Override public void setCola() { kfc.setCola(null); System.out.println("套餐A裡面沒有可樂"); } @Override public void setPizza() { kfc.setPizza(null); System.out.println("套餐A裡面沒有披薩"); } @Override public KFC getKFC() { return kfc; } } ``` 套餐B ```java public class ConcreteBuilder2 implements Builder { private KFC kfc; //這一步非常重要 public ConcreteBuilder2(String hamburger,String chips){ kfc = new KFC(hamburger,chips); } @Override public void setChicken() { kfc.setChicken("小雞腿"); } @Override public void setCola() { kfc.setCola("小可樂"); } @Override public void setPizza() { kfc.setPizza("小披薩"); } @Override public KFC getKFC() { return kfc; } } ``` Director: 真正的執行者,這裡把他當作服務員,此時你像服務員點餐 ```Java public class Director { public KFC build(Builder builder){ //套餐裡面我只選了雞腿和可樂 builder.setChicken(); builder.setCola(); return builder.getKFC(); } } ``` 測試 ```java public class BuilderTest { public static void main(String[] args) { //套餐A System.out.println("======套餐A======"); Builder concreteBuilder1 = new ConcreteBuilder1("大漢堡", "小薯條"); KFC kfc1 = new Director().build(concreteBuilder1); System.out.println(kfc1); //套餐B System.out.println("======套餐B======"); Builder concreteBuilder2 = new ConcreteBuilder2("小漢堡", "小薯條"); KFC kfc2 = new Director().build(concreteBuilder2); System.out.println(kfc2); } } ``` 輸出 ![](https://img2020.cnblogs.com/blog/1988391/202005/1988391-20200503140105745-1283165455.png) ``` 到了這裡你還是會覺得有點麻煩,你會發現,單品可有可無的選擇上面你十分的被動,程式碼看上去也很怪,如果你下次想全部單品先選上,再去選套餐的時候,你又要新建一個新的指導者。 ``` 我覺得普通的建造者模式不適合引數的可有可無的選擇,**普通的建造者模式更側重調控次序**,在有些情況下需要簡化系統結構
## 四、簡化版的建造者模式 這個時候簡化版的建造者模式站出來了 ##### **採用鏈式程式設計的方式** 這種模式更加靈活,更加符合定義 既然Director是變化的,並且其實在生活中我們自己本身就是Director,所以這個時候我們可以**把Director這個角色去掉**,因為我們自身就是指導者 * 產品(product) ```java public class KFC { //套餐必點 private String hamburger; private String chips; //套餐選點 private String chicken; private String cola; private String pizza; public KFC(String hamburger,String chips){ this.hamburger = hamburger; this.hamburger = chips; } public void setChicken(String chicken) { this.chicken = chicken; } public void setCola(String cola) { this.cola = cola; } public void setPizza(String pizza) { this.pizza = pizza; } } ``` * 抽象建造者(builder) ```java public abstract class Builder { abstract Builder setChicken(); abstract Builder setCola(); abstract Builder setPizza(); abstract KFC getKFC(); } ``` * 具體建造者(ConcreteBuilder) ```java public class ConcreteBuilder extends Builder { KFC kfc; public ConcreteBuilder(String hamburger,String chips){ kfc = new KFC(hamburger,chips); } @Override Builder setChicken() { kfc.setChicken("雞腿"); return this; } @Override Builder setCola() { kfc.setCola("可樂"); return this; } @Override Builder setPizza() { kfc.setPizza("披薩"); return this; } @Override KFC getKFC() { return kfc; } } ``` * 測試 ```java public class BTest { public static void main(String[] args) { KFC kfc = new ConcreteBuilder("漢堡","薯條").setChicken().setCola().getKFC(); } } ``` 如果不需要抽象建造者的角色來規定生產內容,那麼程式碼到這裡其實還有進一步的簡化空間。 【關鍵程式碼】 ##### **使用靜態內部類的方式** 【進一步簡化】 ```java public class KFC { //套餐必點 private String hamburger; private String chips; //套餐選點 private String chicken; private String cola; private String pizza; //一定要有一個帶有Builder引數的建造者 private KFC(Builder builder) { this.hamburger = builder.hamburger; this.chips = builder.chips; this.chicken = builder.chicken; this.cola = builder.cola; this.pizza = builder.pizza; } //注意必須為靜態內部類 public static class Builder{ //套餐必點 private String hamburger; private String chips; //套餐選點 private String chicken; private String cola; private String pizza; public Builder(String hamburger,String chips){ this.hamburger = hamburger; this.chips = chips; } public Builder setChicken(){ this.chicken = "小雞腿"; return this; } public Builder setCola(){ this.cola = "小可樂"; return this; } public Builder setPizza(){ this.pizza = "小披薩"; return this; } //生成一個產品 public KFC getKFC(){ return new KFC(this); } } } ``` 測試 ```java public class BuilderTest { public static void main(String[] args) { KFC kfc = new KFC.Builder("大漢堡", "小薯條").setChicken().setCola().getKFC(); System.out.println(kfc); } } ```
## 五、建造者模式和抽象工廠模式的區別 通過上面的程式碼,你發現普通的建造者模式和抽象工廠模式真的很像,在建造者模式中的builder角色很像超級工廠,然後contracterBuilder很像具體的工廠,都是規定了建造的內容 那麼它們之前 有什麼區別呢 * 建造者模式有指導者這個角色,直接返回一個組裝好的產品,而抽象工廠模式返回一系列相關的產品,這些產品位於不同的產品等級結構,構成了一個產品族 * 建造者模式更適合複雜的產品構建 * 可以將抽象工廠模式理解成汽車零件生產工廠,而建造者模式看出組裝工廠 【總結】 學習一類技巧是為了解決一類問題,學習設計模式主要是為了理解它的思想,將來遇到程式碼的編寫,使用這種模式會更加的方式,而沒有必要刻意的去使用,但是需要刻意的去練習,形成這