1. 程式人生 > >設計模式(四):從“兵工廠”中探索簡單工廠、工廠方法和抽象工廠模式

設計模式(四):從“兵工廠”中探索簡單工廠、工廠方法和抽象工廠模式

前面陸陸續續的更新了三篇關於設計模式的部落格,是關於“策略模式”、“觀察者模式”、“裝飾者模式”的,今天這篇部落格就從“兵工廠”中來探索一下“工廠模式”(Factory Pattern)。“工廠模式”又可以分為“簡單工廠模式”(Simple Factory Pattern)“工廠方法模式”(Factory Method Pattern)“抽象工廠模式”(Abstract Factory Pattern)。今天這篇部落格就從頭到尾的來介紹一下這三種模式,並且給出相應的Swift程式碼的實現。在文章的最後會給出“工廠方法模式”和“抽象工廠模式”的使用場景,並在一個示例中將兩者結合起來使用。同樣,部落格最後仍然會給出示例在github上的分享連結。

工廠模式,顧名思義,肯定和工廠有關的模式。在現實生活中,我們都知道工廠是賦值生成產品的,也就是說工廠往外輸出不同的產品,這些產品可以是同一型別,也可以是不同的型別。在我們設計模式中的工廠也是用來生產產品的,不過此產品非比產品。工廠模式中的工廠負責生產“物件”,該工廠也就是物件的工廠。我們在使用工廠模式時,需要使用哪種型別的物件,我們就告訴“工廠”,工廠就會根據我們的指令來生產出相應型別的物件。

工廠模式的應用場景大部分是當你根據不同型別來生成不同物件時,就可以使用工廠模式來解決。也就是說將建立物件的過程封裝到工廠中來完成。接下來我們將會通過兵工廠造武器的例子來好好的聊一下工廠模式。這個兵工廠的示例我們先不使用工廠模式來實現出來,然後在通過“簡單工廠”、“工廠方法”以及“抽象工廠”模式來實現出來。當然下方我們還會用到“裝飾者模式”,關於裝飾者模式的詳情請參見《》。廢話少說,進入今天的主題。

一、建立無"兵工廠"的武器庫

這一部分我們先給出不使用任何型別的“工廠模式”來實現我們的武器庫。該示例與之前我們介紹《》使用的示例是一直的,不過之前我們側重於“策略模式”,而如今我們注重於“工廠模式”。當然這兩者可以共存,不過今天我們的重點是“工廠模式”,所以就不對“策略模式”進行討論了。下方是我們將要實現示例的類圖。

從下方的類圖不難看出,WeaponType是武器型別的介面(協議),其中有一個必須實現的方法就是fire()方法。AK、AWP、HK都實現了WeaponType協議。WeaponUser則是武器的使用者,WeaponUser與WeaponType當然是依賴關係。下方就是沒有使用“工廠方法”的類圖。

     

從上面的類圖來看,實現我們的程式碼是並不困難的。下方就是WeaponType協議與武器AK、AWP、HK的具體實現。在每種武器實現時都有一個fire()方法,每種武器的fire()都有其獨特之處。

    

實現完各種武器後,我們得建立一個武器的使用者WeaponUser。武器的使用者根據武器的型別來使用武器。下方是武器的使用者WeaponUser、武器列舉WeaponTypeEnumeration、測試用例以及輸出結果的截圖。WeaponUser中的fireWithType()方法就是根據不同的武器型別來建立不同的武器物件然後在呼叫武器的fire()方法。下方WeaponUser是直接對武器進行的建立,未用到工廠模式。

   

二、“簡單工廠”(Simple Factory)

由易到難,緊接著我們要使用“簡單工廠”模式對上述進行重寫。那麼我們為什麼要使用“簡單工廠”模式進行重寫呢?因為在之前我們“重構”的系列部落格中也不止一次的提到過,要將變化的部分與不變的部分進行分離,對變化進行封裝。在上面的示例中,WeaponUser中的Swift-Case語句不難看出,隨著武器種類的增加或者減少,這個Switch語句就會隨之改變,後期會對此進行修改,可是WeaponUser中的其他部分是不變的。

本著將變化進行封裝的原則,我們將上面的Switch語句進行封裝,將其放到一個新的類中,在我們的WeaponUser中使用這個新封裝的類來建立不同的武器。對上述示例變化的內容提取封裝,於是就是形成了我們的“簡單工廠模式”。這個新提取被封裝的新的建立武器的類就是我們的工廠類,其職責就是用來建立各種各樣的武器,這個簡單工廠就是我們的“兵工廠”。下方就是我們“簡單工廠”的類圖,與第一部分的類圖相比,不難發現在WeaponUser與WeaponType之間多了一個WeaponFactory,這個WeaponFactory就是我們的簡單工廠。類圖如下:

    

有上面的“類圖”可以給出其程式碼實現。當然下方的程式碼是對上述示例中的WeaponUser類的重構。下方這個簡單的兵工廠類WeaponFactory就是我們封裝的簡單工廠。其中有一個方法專門負責使用武器型別來建立武器物件,這也符合“單一職責”的原則。該方法比較簡單,在此就不做過多的贅述了。下方的兵工廠類就負責生產武器物件,具體實現如下所示:

   

實現完我們的簡單工廠後,緊接著要修改我們的WeaponUser類。WeaponUser類依賴於上面的WeaponFactory工廠類,因為WeaponUser要使用WeaponFactory來建立不同的武器物件。WeaponUser類在使用了“簡單工廠”後的程式碼如下,以及測試用例和輸出結果如下所示:

   

三、“工廠方法模式”(Factory Method Pattern)

第二部分的“簡單工廠”模式確實簡單,緊接著隨著需求的變更,我們需要為上述功能新增新的需求。武器不僅僅有種類,還有生產廠家,我們要為武器新增生產廠家。比如美國造AK, 德國造AK。則我們的使用者可以分為美國武器使用者(AmericaWeaponUser),德國武器使用者(GermanyWeaponUser)。在這種情況下,“簡單工廠”模式已不再使用。所以我們要使用即將出場的“工廠方法模式”(Factory Method Pattern)和“策略模式”(Strategy Pattern)對上述示例進行擴充。

在對示例進行擴充之前呢,我們先看一下“工廠方法模式”的定義,其中使用到了依賴倒置原則,也就是要依賴抽象而不依賴具體類。這一點與“面相介面程式設計而不面相實現程式設計有些類似”,下方是工廠方法模式的定義。

工廠方法模式:定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個。工廠方法讓類把例項化推遲到子類。

1.“工廠方法模式”的類圖

如果你看完定義後,感覺抽象,那麼沒關係,看完下方的示例在看上述定義就會一目瞭然。下方就是我們要實現的“工廠方法模式”的類圖,也是在上面類圖的基礎上進行擴充的。下方“類圖”中不僅僅至於“工廠方法模式”的部分,還有我們之前介紹過的裝飾者模式。為了給武器新增生產廠商,我添加了“GermanyDecorator”德國造裝飾器和“AmericaDecorator”美國造裝飾器,下方圖中的紅框部分就是我們為武器新增的裝飾者,關於裝飾者模式的詳情請參見《花瓶+鮮花”中的裝飾模式(Decorator Pattern)》。

下方“類圖”中綠框中是我們該部分的主題,也就是我們“工廠方法模式”的核心。由下方類圖不難看出,與我們上面的“簡單工廠模式”類比的話,下方的“工廠方法模式”是沒有一個獨立的的工廠的類的,但是他有一些工廠方法。這些工廠方法的實現位於WeaponUser的子類中,由子類來確定生產出什麼種類的武器。這也就是上面工廠方法模式定義中所說的“將物件的例項化推遲到子類中”。“工廠方法模式”重點在於方法,利用繼承關係,以及子類間的差異化類建立不同的武器型別。AmericaWeaponUser(美國武器使用者)的createWeaponWithType()工廠方法就會建立美國造武器(為武器新增上“美國造”裝飾),同理GermanyWeaponUser(德國武器使用者)的createWeaponWithType()工廠方法就會建立德國造武器(為武器新增上“德國造”裝飾),這就是“工廠方法模式”的核心。有一點需要注意,同一個工廠方法中生產的是同一系列的不同產品,比如美國造的各種武器,這一點與抽象工廠不同。稍後在介紹“抽象工廠模式”時會給出對比。

   

2. 為“武器”新增裝飾者(程式碼實現)

首先了我們為武器新增裝飾者,也就是上方類圖中紅框部分的“裝飾者模式”的程式碼實現。還是那句話,因為“裝飾者模式”在之前的部落格中已經進行了詳細的介紹,今天就不詳述了,直接給出其程式碼實現吧。下方的程式碼片段就是武器的兩個裝飾者,如下:

     

3.武器使用者介面的程式碼實現

下方截圖中是武器使用者介面WeaponUser的實現,介面中聲明瞭一個賦值開火(fireWithType())的方法,和一個“工廠方法”(createWeaponWithType())。在WeaponUser中我們緊接著給出了fireWithType()方法的預設實現,在fireWithType()方法中呼叫了相應的“工廠方法”來獲取相應的武器型別,具體實現如下。

   

4.“工廠方法”的具體實現

當然在“工廠方法”模式中,工廠方法的具體實現我們是推遲到相應的子類中來完成的。在該示例中就是在GermanyWeaponUser和AmericanWeaponUser中來實現具體的工廠方法。在GermanyWeaponUser(德國武器使用者)中的工廠方法給不同種類的武器新增上了“德國造”裝飾器,同樣在AmericanWeaponUser中的工廠方法中也做了類似的事情。具體程式碼如下所示:

   

5.測試用例

上面就是我們的全部程式碼了,接下來就到了我們測試的時候了,下方截圖就是我們的測試用例以及輸出結果。因測試用例比較簡單,在此就不做過多贅述了,一句話:德國使用者使用德國造,美國使用者使用美國造。

    

四、“抽象工廠模式”(Abstract Factory Pattern)

上面我們使用到了“工廠方法模式”以及“裝飾者模式”,接下來我們要做的事情就是將上面的“工廠方法模式”改為“抽象工廠模式”(Abstract Factory Pattern)。我們在改的過程中,不會動“工廠方法”一以外的部分,我們只重寫第三部分中類圖的綠框部分。簡單的說,就是將上面的“工廠方法模式”替換為“抽象方法模式”,然後在對比兩者的異同。當然本部分是“抽象工廠” + “裝飾者”,而上一部分是“工廠方法” + “裝飾者”

下方是“抽象工廠的定義”

抽象工廠:提供一個介面,用於建立相關或依賴物件的家族,而不需要明確指定具體類。

1.設計“類圖”

定義一般是不太好理解的。言歸正傳,還是老套路,先來看一下我們將要實現的類圖是怎樣的。該部分的“類圖”與第三部分的類圖非常相似,因為是在第三部分的類圖上修改的。下方“類圖”中紅框的部分是我們未修改的部分,與第三部分中的類圖一致。而綠框中則是我們使用“抽象工廠模式”重寫後的結果。“抽象工廠”顧名思義,就是抽象了的工廠,這個抽象你可以使用介面、協議、抽象類來實現。

下方綠框中的WeaponFactoryType協議就是我們的“抽象工廠”,而AmericanWeaponFactory,GermanyWeaponFactory是其不同種類的具體實現。WeaponUser就是武器使用者,對於WeaponUser來說,它是依賴於介面,而不依賴於具體實現的,這也是設計模式的一大準則。有下方的“類圖”不難看出,“抽象工廠”是一些特定工廠的集合,也就是組合的關係。具體的工廠中生產的同一品牌的不同產品。而“工廠方法”就與此不同了,“工廠方法”就是這一系列產品的具體實現。下一部分我們會在下方“抽象工廠”基礎上新增上“工廠方法模式”,下方會給出具體實現,本部分還是主要了解“抽象工廠模式”。

   

2. 程式碼實現

 有了類圖了,我們再去實現我們的程式碼就容易多了。我們只需要在第三部分的基礎上將“工廠方法”替換為抽象工廠即可。下方程式碼就是“抽象兵工廠”、“美國兵工廠”、“德國兵工廠”的實現程式碼。在“美國兵工廠”中使用了AmericaDecorator裝飾器,在“德國兵工廠”中使用了GermanyDecorator裝飾器。每個“兵工廠”都創造了其獨居特色的武器裝備。

    

實現完工廠後,我們要修改武器的使用使用者。因為在“工廠方法”模式中,不同工廠武器的選擇是在使用者的子類中實現的,而在“抽象工廠”中就使用不到子類了。“抽象工廠”模式的使用者與“簡單工廠”模式的使用者非常相似。下方就是我們“抽象工廠”的使用者,其依賴於“抽象工廠”介面,具體實現如下所示:

  

3.測試用例

程式碼實現完了,怎麼能少的了測試用例呢,下方就是我們要使用的測試用例。武器使用者可以自由切換生產武器的工廠,其實這一點類似於“策略模式”,不過不是策略模式,可以新增上一下相應的方法,將上述實現融進策略模式,再次就不做過多贅述了。下方就是我們的測試用例:

   

五、“工廠方法”+“抽象工廠”模式

緊接著,我們要做一件事情,就是保留第四部分中的“裝飾者”、“抽象工廠”,在此基礎上新增上“工廠方法”模式。也就是我們要實現裝飾者+抽象工廠+工廠方法,是不是聽起來很興奮呢。上面也說了,還可以融入“策略模式”,這個就留給讀者去做了。在本部分,我們將使用“工廠方法”模式來重寫武器的使用者WeaponUser。這一部分很好的說明了工廠方法模式與抽象工廠模式的不同,而且兩者可以結合在一起使用。

1、設計“類圖”

下方這個“類圖”就比較複雜了,不過不用擔心,下方是在上一部分擴充套件而來的。與第四部分的類圖對比一下,是不是隻添加了黃框中的內容呢?確實如此,本部分在上面設計的基礎上,我們添加了“工廠方法模式”,關於工廠方法模式的具體內容請參見本篇部落格中的第三部分。紅框中的裝飾者模式與綠框中的“抽象工廠模式”是不變的。我們只是使用“工廠方法模式”重寫了第四部分中的WeaponUser類。將WeaponUser類中選擇工廠的程式碼推遲到了相應的子類中,有子類的工廠方法來完成相應工廠的建立

   

2.程式碼實現

如果你看類圖比較抽象的話,那麼我們就看程式碼。在實現中我們隊WeaponUser類進行的“工廠方法”的重寫。提取了WeaponUser的介面WeaponUserType(對應著上述類圖中的WeaponUser)。下方就是WeaponUserType的程式碼實現,在提取介面中,其中有一個“工廠方法”,那就是createWeaponFactroy(),如下所示:

   

為了程式碼的複用性,我們為上述介面中一些方法提供預設的實現。當然在Swift中我們使用extension為協議新增預設實現。在下方的延展中我們給出了fireWithType()和createWeaponWithType()的預設實現。具體做法如下所示。

    

緊接著我們要實現相應的WeaponUserType的子類,在子類中通過“工廠方法”來指定兵工廠,具體實現如下:

    

3.測試用例

至此,我們又在“裝飾者”、“抽象工廠”的基礎上新增上了“工廠方法模式”。通過上面的案例我們不難看出,“抽象工廠”是工廠的集合,“工廠方法”會指定使用工廠集合中某一個特定的工廠。下方是對本部分案例的測試用例,具體如下:

   

今天的部落格篇幅有限,就先到到這兒了,其實上述示例還是可以加入“策略模式”的,感興趣的讀者可以嘗試一下。