在我的上一篇文章(疑惑?改良?從簡單工廠到Factory 方法)中,詳細論述了建立模式中簡單工廠到Factory 方法的演變過程,並試圖結合Factory 方法的設計以及.net中的反射機制之所長,改良出一種新型的工廠—反射工廠,這當然不是我的首創,經典的PetShop 中便有此工廠的身影。本文嘗試按照前篇文章的思路,藉著Factory 方法到抽象工廠的演變過程而繼續對抽象工廠進行改良,文章中的思想僅代表了作者當時的觀點,有欠妥的地方,還請各位不吝賜教。
原廠模式
前面的文章提到了簡單工廠和Factory 方法其實是一碼事,他們完成了將客戶對產品功能的使用與建立具體產品職責的分割,不同的只不過是他們實現方式上的差異,Factory 方法利用更加優雅的多態性取代了相對ugly的switch case…語句,從而很好的體現了設計原則中的OCP原則,此文章將不再強調這種實現上的差異性,而更多的強調兩者之間設計思路上的共性,並將這種共性統稱成為原廠模式,從而進一步與抽象工廠進行對比。
工廠的使用,選擇的過程
原廠模式的使用,實際上是客戶(產品的消費者)對於產品選擇的過程,對於實現了相同功能的產品來講,客戶更加關心的是產品間的差異性,而工廠的作用則是將產品的生產過程封裝,根據客戶的要求直接返回客戶需要的產品。注意,工廠只是隱藏了產品的生產過程,但是並沒有剝奪客戶選擇的權利,那麼客戶的這個選擇過程又是如何體現的呢?在簡單工廠中,客戶通過參數的形式告訴工廠需要什麼樣的產品,而在Factory 方法中,客戶通過對工廠的選擇代替了直接對產品的選擇,注意到Factory 方法中一個工廠只有一個Create方法,也就是說一個工廠只負責生產一種產品,那麼你選擇了相應的工廠也就等同於你選擇了對應的產品。就連改良後的反射工廠也沒有消去對產品的選擇,只不過是將這種選擇外化(外化到設定檔中,從而使得對代碼的改動最小)。可以說,正是由於產品間的差異性帶給了客戶選擇的權利,這種權利是不應當被工廠取代的,那麼原廠模式的價值又在哪裡呢?答案是抽象與封裝,原廠模式將由於客戶的不同選擇而可能導致的對已知事物的影響降到最低,途徑是通過抽象產品取代具體產品,使得客戶依賴於抽象(越抽象的東西越穩定),同時將客戶的選擇封裝到一處,隔離與具體產品間的依賴。
原廠模式與抽象工廠
前面說了這麼多無關的,為得是做好鋪墊,更加有益於對下文的理解,OK,終於該說說從原廠模式到抽象工廠的轉變了,先來對比兩張類圖:
Factory 方法(Factory Method)
抽象工廠(Abstract Factory)
我們能夠看到哪些差異?
最明顯的一點就是在Factory 方法的類別關係圖中只有一類產品,他們實現了一個統一的介面,而抽象工廠有多個類別的產品,他們分別實現了不同的功能(多個介面)。其次的一點差別就是工廠本身所具有的方法數量不同,這點差異其實也是由第一點所導致的,工廠需要有生產不同類別產品的功能,如果抽象工廠中的產品的功能簡化到一個,也便成為了Factory 方法。
引出類別的概念,類別是指具有相同功能的一類產品的總稱。
再來看選擇的過程,在Factory 方法中,客戶關心的只不過是實現同一樣功能的不同產品間的差異,這是一個一維選擇的過程。
1 IFactory factory = new FactoryA(); //選擇了工廠即選擇了產品
2 IProduct productA = factory.Create(); //工廠只有一個Create方法,只能生產一種產品
而在抽象工廠中,客戶首先要考慮的是需要哪一樣功能,其次要考慮才是產品間的差異。也就是說這是一個二維選擇的過程。
1 IFactory factory = new FactoryA(); //選擇了某個具體工廠
2 IProduct productA = factory.CreateProductA(); //工廠具有多個Create方法,這裡選擇了其中的一個
由於產品類別的增加而導致客戶在考慮產品間差異的同時還要考慮產品間功能的差異,這種二維選擇的過程才是Factory 方法與抽象工廠之間的本質區別。
舉個肯德基與麥當勞的例子,假設原來只有一家快餐店叫做麥當勞,提供的食物(具體產品)有漢堡、可樂、薯條,它們都可以滿足你吃東西(抽象介面)的需求,那麼你想吃快餐的時候,唯一的選擇就在於吃什麼,是一維選擇,現在又開了一家快餐店叫做肯德基,同樣供應漢堡、可樂和薯條,那麼現在你若打算吃快餐,除了考慮吃什麼外,還要考慮去哪裡吃--肯德基還是麥當勞?這便是二維的選擇。通過橫向與縱向的選擇才能最終鎖定你要的產品。
引入系列的概念,相互間具有差異的同一類別的產品稱為不同的系列,如肯德基和麥當勞就是兩個不同的系列。
這種選擇的區別帶來的另外一個後果就是產品間的差異(系列間的差異)變為客戶的次要選擇,而客戶主要的精力放在了功能的選擇上(類別的選擇)。
我們結合執行個體來看看抽象工廠的一般設計及實現
客戶調用代碼
1 AbstractFactory factory = new McDFactory(); //選擇去麥當勞吃
2 IHamburger hamburger = factory.CreateHamburger(); //選擇吃漢堡
可以看到系列間的選擇由工廠的選擇來替代,而類別間的選擇實際上就是工廠內不同類別產品建立方法的選擇。那麼如此這般設計是否有依據呢?我們為什麼不將工廠設計成為漢堡工廠,可樂工廠和薯條工廠呢?這其實是剛接觸抽象工廠的人經常陷入的誤區,這個問題的關鍵在於需求!需求是來自於客戶的,工廠的設計取決於客戶怎樣使用產品。在這個例子中,之所以要將漢堡、可樂和薯條設計為類別而不是系列,首先是因為他們對於客戶有不同的功能,例如漢堡可以充饑,薯條可以解饞、可樂可以解渴,任何一個客戶希望走進一家店(無論是肯德基還是麥當勞)都能夠得到上面的這三種功能,這才是客戶真正的需求!如果客戶選擇走進了麥當勞,那麼他就永遠不會吃到肯德基的食品。這裡有點繞可能需要多想一下,再舉個例子協助理解,比如星際爭霸遊戲,裡面有三個種族,每個種族的兵營都能生產三種兵,如何選定類別與系列?拋開遊戲的設計來講,從使用者的角度出發,我們還是會把系列對應成種族,那是因為任何一個玩家進入遊戲後,都希望能夠體驗三個不同的兵種所具有的不同能力,而不是希望擁有一個能夠製造出所有三個種族一級兵的兵營。反過來講,使用者每次進入遊戲只可能選擇一個種族,如果他這次選擇了蟲族,那麼他就永遠不可能生產出機槍兵。
最後再將上面提到的種種概念進行一下總結
類別 = 介面 = 抽象產品 = 具體產品所具有的共同功能,通過採用工廠的某個具體方法來體現選擇。
系列 = 抽象工廠 = 同一類別間不同產品的差異,通過對執行個體化某個具體工廠來體現選擇。
反射機制實現選擇邏輯,關於工廠的工廠
有人的地方就有恩怨,有恩怨的地方就有江湖,人就是江湖,你怎麼退出?
有選擇的地方就有工廠,由工廠的地方就有反射,選擇就是反射,你怎麼設計?
呵呵… 開個玩笑,其實上面所寫的幾段廢話就是為了論證這一個觀點,有選擇的地方就可以反射。看看我是如何把Factory 方法改良成反射工廠的,既然Factory 方法是一維的選擇可以反射,那麼對於抽象工廠這個二維的選擇自然更不在話下,仔細觀察工廠的選擇過程。
1 AbstractFactory factory = new McDFactory(); //選擇去麥當勞吃
2 IHamburger hamburger = factory.CreateHamburger(); //選擇吃漢堡
發現選擇分別是針對工廠的選擇以及方法的選擇,然而對於工廠內部方法的選擇不適用於反射機制,因為不同的方法實現了不同的功能,相互間沒有統一的介面,何談反射,那麼反射自然就只能放在對於工廠的選擇上--它們都繼承自抽象工廠。於是我們建立一個反射工廠用來動態產生需要的具體工廠,即工廠的工廠。話說起來彆扭,還是看圖。
這裡新添加的FFactory就是反射工廠 ,對於反射工廠來說,Abstract Factory就是抽象產品,FactoryA和FactoryB是相應的具體產品。
1 <appSettings>
2 <add key="factoryName" value="FactoryA"/>
3 </appSettings>
反射工廠
1 public static IFactory CreateFactory()
2 {
3 //從設定檔中讀取需要執行個體化的工廠
4 string factoryName = ConfigurationSettings.AppSettings["factoryName"].ToString();
5
6 AbstractFactory factory;
7 factory = (AbstractFactory)Assembly.Load("Abstract Factory").CreateInstance("Abstract_Factory.ImproveFactory." + factoryName);
8 return factory;
9 }
客戶使用
1 public void Run()
2 {
3 AbstractFactory factory = FFactory.CreateFactory();//生產工廠的工廠,採用反射機制
4 IProduct1 product1 = factory.CreateProduct1();
5 IProduct2 product2 = factory.CreateProduct2();
6
7 product1.DoTask1();
8 product2.DoTask2();
9 }
補充
抽象工廠的使用除了對多系列多類別的應用外,還有一點很重要的原則,即它的一系列的產品總是在一起使用的,只有這樣才更能體現出抽象工廠的價值,關於這點我會在下一篇中通過與Builder模式作對比來闡述兩者間的相同與異同。