1. 程式人生 > >工廠模式-將物件的建立封裝起來

工廠模式-將物件的建立封裝起來

> **公號:碼農充電站pro** > **主頁:** 工廠模式(*Factory Design Pattern*)可細分為三種,分別是**簡單工廠**,**工廠方法**和**抽象工廠**,它們都是為了更好的建立物件。 所謂的“工廠”,就是用來將**建立物件的程式碼**封裝起來,因為這部分程式碼將來變動的機率很大,所以這裡的“工廠”的實質作用就是“封裝變化”,以便於維護。 其中用到了“**針對介面程式設計,而非針對實現程式設計**”的設計原則。 下面通過一個銷售飲料的例子,來對這三種工廠模式進行介紹。 ### 1,簡單工廠模式 假如現在有一位老闆,想開一家飲料店,該店銷售各種口味的飲料,比如蘋果味,香蕉味,橙子味等。 將這些飲料用程式碼來表示,如下: ```java class Drink { public void packing() { // } } class DrinkApple extends Drink { } class DrinkBanana extends Drink { } class DrinkOrange extends Drink { } ``` `Drink` 類為所有其它味道的飲料的父類。 當有顧客來購買飲料的時候,顧客需要說明想買哪種口味的飲料,然後服務員就去將該口味的飲料取過來,包裝好,然後交給顧客。 下面我們用最簡單直接的程式碼,來模擬飲料店和賣飲料的過程,如下: ```java class DrinkStore { public Drink sellDrink(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } drink.packing(); return drink; } } ``` 但是這種實現方式有個問題,就是當需要下架舊飲料或上架新飲料的時候,會導致下面這部分程式碼被頻繁的修改: ```java if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } ``` 那這就違背了設計原則中的**開閉原則**:*程式碼應該對擴充套件開發,對修改關閉*。所以我們需要對該程式碼進行改進,那如何修改呢? **簡單工廠模式**告訴我們要將類似這樣的程式碼封裝到一個**類**裡邊,這個類就叫做**簡單工廠類**,該類中提供一個**方法**,它可以**生產**我們所需要的各種物件。 下面用程式碼來模擬這個**簡單工廠類**,如下: ```java class SimpleDrinkFactory { public Drink createDrink(String flavor) { Drink drink; // 這段容易被頻繁修改的程式碼,被封裝到了工廠類中 if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } return drink; } } ``` 可以看到,`createDrink` 方法完成了建立 `Drink` 的任務。 `createDrink` 方法也可以定義成**靜態方法**,優點是在使用 `createDrink` 方法時不需要再建立物件,缺點是不能再通過繼承的方式來改變 `createDrink` 方法的行為。 下面來看如何使用 `SimpleDrinkFactory` 類: ```java class DrinkStore { private SimpleDrinkFactory factory; public DrinkStore(SimpleDrinkFactory factory) { this.factory = factory; } public Drink sellDrink(String flavor) { Drink drink = factory.createDrink(flavor); drink.packing(); return drink; } } ``` 可以看到,我們將 `SimpleDrinkFactory ` 類的物件作為 `DrinkStore` 類的一個屬性,經過改進後的 `sellDrink` 方法就不需要再被頻繁修改了。如果再需要上架下架飲料,則去修改簡單工廠類 `SimpleDrinkFactory` 即可。 我將完整的**簡單工廠**程式碼放在了[這裡](https://github.com/codeshellme/codeshellme.github.io/blob/master/somecode/dp/SimpleFactory.java),供大家參考,類圖如下: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201223163058254.png?) ### 2,工廠方法模式 簡單工廠模式從嚴格意義來說並不是一個設計模式,而更像一種程式設計習慣。 **工廠方法模式**定義了一個建立物件的介面(該介面是一個抽象方法,也叫做“工廠方法”),但由子類(實現抽象方法)決定要例項化的類是哪個。 在工廠方法模式中,父類並不關心子類的具體實現,但是父類給了子類一個“規範”,讓子類必須“生成”父類想要的東西。 工廠方法將類的例項化推遲到了子類中,讓子類來控制例項化的細節,也就是將建立物件的過程**封裝**了起來。而真正使用**例項**的是父類,這樣就將例項的“實現”從“使用”中解耦出來。因此,工廠方法模式比簡單工廠模式更加有“彈性”。 下面我們來看下如何用**工廠方法模式**來改進上面的程式碼。 首先,定義 `DrinkStoreAbstract `,它是一個抽象父類: ```java abstract class DrinkStoreAbstract { // final 防止子類覆蓋 public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用例項 drink.packing(); return drink; } // 子類必須實現 protected abstract Drink factoryMethod(String flavor); } ``` 上面程式碼中 `factoryMethod` 方法就是所謂的工廠方法,它是一個抽象方法,子類必須實現該方法。 `factoryMethod` 方法負責生成物件,使用物件的是父類,而實際生成物件的則是子類。 接下來定義一個具體的 `DrinkStore` 類,它是 `DrinkStoreAbstract` 的子類: ```java class DrinkStore extends DrinkStoreAbstract { @Override public Drink factoryMethod(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } return drink; } } ``` 可以看到,子類中的 `factoryMethod` 方法有了具體的實現。 如果需要上架下架飲料,則去修改子類中的工廠方法 `factoryMethod` 即可。而 `DrinkStoreAbstract` 作為一個“框架”,無須改動。 完整的**工廠方法**程式碼放在了[這裡](https://github.com/codeshellme/codeshellme.github.io/blob/master/somecode/dp/FactoryMethod.java),供大家參考,類圖如下: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201224005001606.png?) 圖中的粉色區域是工廠方法模式的重點關注區。 ### 3,依賴倒置原則 在介紹**抽象工廠模式**之前,我們先來看看什麼是**依賴倒置原則**。 **依賴倒置**包含了**依賴**和**倒置**兩個詞。 我們先來看看“依賴”,“依賴”是指類與類之間的依賴關係。 在本文剛開始時的 `sellDrink` 方法是這麼寫的: ```java public Drink sellDrink(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } drink.packing(); return drink; } ``` `sellDrink` 方法依賴了三個**具體類**,如果飲料的味道繼續增加的話,那麼 `sellDrink` 方法將依賴更多的**具體類**。 這會導致只要任意一個具體類發生改變,`sellDrink` 方法就不得不去改變,也就是類 **A** 需要改變(A 也是一個具體類)。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201223185509535.png?) 在上面這個關係圖中,`A` 稱為**高層元件**,各個具體類稱為**低層元件**,所以在這幅圖中,高層元件依賴了低層元件。 **依賴倒置原則**是指“**要依賴抽象,而不依賴具體類**”。更具體來說就是,**高層元件不應該依賴低層元件,高層元件和低層元件都應該依賴抽象類**。 那麼怎樣才能達到**依賴倒置原則**呢?**工廠方法**就可以! 在經過工廠方法模式的改造之後,最終的 `DrinkStoreAbstract` 類中的 `sellDrink` 方法變成了下面這樣: ```java public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用例項 drink.packing(); return drink; } ``` 這使得 `sellDrink` 的所在類不再依賴於具體類,而依賴於一個抽象方法 `factoryMethod`,而 `factoryMethod` 方法依賴於 `Drink`,然後,各個具體類也依賴於 `Drink`,`Drink` 是一個廣義上的“抽象介面”。 這樣,高層元件和低層元件都依賴於 `Drink` 抽象,關係圖變成了下面這樣: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201223213201639.png?) 之前各個具體類的箭頭是向下指的,而現在各個具體類的箭頭是向上指的,箭頭的方向**倒了過來**,這就是所謂的**依賴倒置**。 **依賴倒置原則**使得高層元件和低層元件都依賴於同一個抽象。 那怎樣才能避免違反**依賴倒置原則**呢?有下面三個指導方針: - 變數不要持有具體類的引用。 - 需要 new 物件的地方,要改為工廠方法。 - 類不要派生自具體類,而要從抽象類或介面派生。 - 派生自具體類,就會依賴具體類。 - 子類不要覆蓋父類中已實現的方法。 - 父類中已實現的方法應該被所有子類共享,而不是覆蓋。 當然事情沒有絕對的,上面三個指導方針,是應該**儘量做到**,而**不是必須做到**。 ### 4,抽象工廠模式 下面來介紹**抽象工廠模式**。 假如臨近春節,飲料店老闆為了更好的銷售飲料,準備購買一批禮盒,來包裝飲料。為了保證禮盒的質量,規定禮盒只能從特定的地方批發,比如北京,上海等。 那我們就定義一個介面,讓所有的禮盒都派生自這個介面,如下: ```java interface DrinkBoxFactory { String createBox(); } class BeiJingBoxFactory implements DrinkBoxFactory { @Override public String createBox() { return "BeijingBox"; } } class ShangHaiBoxFactory implements DrinkBoxFactory { @Override public String createBox() { return "ShangHaiBox"; } } ``` 下面需要編寫 `Drink` 類,我們讓 `Drink` 類是一個抽象類,如下: ```java abstract class Drink { String flavor; protected abstract void packing(); } ``` 需要注意的是,在抽象類 `Drink` 中有一個抽象方法 `packing`,`Drink` 將 `packing` 的具體實現交給每個派生類。`Drink` 類只規定派生類中需要實現一個 `packing`,但並不關心它的具體實現。 下面編寫每種口味的飲料,如下: ```java class DrinkApple extends Drink { DrinkBoxFactory boxFactory; public DrinkApple(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkApple"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } } class DrinkBanana extends Drink { DrinkBoxFactory boxFactory; public DrinkBanana(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkBanana"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } } class DrinkOrange extends Drink { DrinkBoxFactory boxFactory; public DrinkOrange(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkOrange"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } } ``` 可以看到每種口味的飲料中都有一個 `boxFactory` 物件,在 `packing` 時,從 `boxFactory` 中獲取禮盒。 注意 `boxFactory` 是一個介面類物件,而不是一個具體類物件,因此,`boxFactory` 的具體物件是由客戶決定的。 然後,`DrinkStoreAbstract` 還是沿用工廠方法模式中的定義,如下: ```java abstract class DrinkStoreAbstract { // final 防止子類覆蓋 public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用例項 drink.packing(); return drink; } // 子類必須實現 protected abstract Drink factoryMethod(String flavor); } ``` 然後我們實現使用北京禮盒包裝的**Store** 和使用上海禮盒包裝的**Store**,如下: ```java class BeijingDrinkStore extends DrinkStoreAbstract { @Override protected Drink factoryMethod(String flavor) { Drink drink = null; DrinkBoxFactory factory = new BeiJingBoxFactory(); if (flavor.equals("apple")) { drink = new DrinkApple(factory); } else if (flavor.equals("banana")) { drink = new DrinkBanana(factory); } else if (flavor.equals("orange")) { drink = new DrinkOrange(factory); } return drink; } } class ShangHaiDrinkStore extends DrinkStoreAbstract { @Override protected Drink factoryMethod(String flavor) { Drink drink = null; DrinkBoxFactory factory = new ShangHaiBoxFactory(); if (flavor.equals("apple")) { drink = new DrinkApple(factory); } else if (flavor.equals("banana")) { drink = new DrinkBanana(factory); } else if (flavor.equals("orange")) { drink = new DrinkOrange(factory); } return drink; } } ``` 經過這麼一些列的改動,我們到底做了些什麼呢?事情的起因是老闆需要一些禮盒來包裝飲料,這就是需求增加了。 因此我們引入了一個**抽象工廠**,即 `DrinkBoxFactory `,這個介面是所有禮盒工廠的父類,它給了所有子類一個“規範”。 **抽象工廠模式**提供了一個介面,用於建立相關物件的家族。**該模式旨在為客戶提供一個抽象介面**(本例中就是 `DrinkBoxFactory ` 介面),從而去建立一些列相關的物件,而不需關心實際生產的具體產品是什麼,這樣做的好處是**讓客戶從具體的產品中解耦**。 最終,客戶是這樣使用 `DrinkStoreAbstract` 的,如下: ```java public class AbstractMethod { public static void main(String[] args) { DrinkStoreAbstract beiStore = new BeijingDrinkStore(); beiStore.sellDrink("apple"); beiStore.sellDrink("banana"); beiStore.sellDrink("orange"); DrinkStoreAbstract shangStore = new ShangHaiDrinkStore(); shangStore.sellDrink("apple"); shangStore.sellDrink("banana"); shangStore.sellDrink("orange"); } } ``` 完整的**抽象工廠模式**程式碼放在了[這裡](https://github.com/codeshellme/codeshellme.github.io/blob/master/somecode/dp/AbstractMethod.java),供大家參考,類圖如下: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20201224004608162.png?) 從該圖可以看出,抽象工廠模式比工廠方法模式更復雜了一些,另外仔細觀察它們兩個的類圖,**各自所關注的地方(粉紅色區域)也是不一樣的**。 工廠方法與抽象工廠的相同點都是將物件的建立封裝起來。不同點是: - **工廠方法主要關注將類的例項化推遲到子類中**。 - **抽象工廠主要關注建立一系列相關的產品家族**。 ### 5,總結 工廠模式使用到的設計原則有: - 針對介面程式設計,而非針對實現程式設計。 - 開閉原則。 - 依賴倒置原則。 - 封裝變化。 本篇文章介紹了三種工廠模式,工廠模式在實際應用中非常廣泛,比如 **Java** 工具類中的 **Calendar** 和 **DateFormat** 等。 (本節完。) --- **推薦閱讀:** [單例模式-讓一個類只有一個例項](https://www.cnblogs.com/codeshell/p/14177102.html) [設計模式之高質量程式碼](https://www.cnblogs.com/codeshell/p/13968620.html) --- *歡迎關注作者公眾號,獲取更多技術乾貨。* ![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic