前面分析了簡單工廠模式和工廠方法模式,接着來看一下抽象工廠模式,他與工廠方法模式有一些相似的地方,也有不同的地方。
先來看一個不用工廠方法模式實現的訂購披薩的代碼:
對象依賴的問題:當你直接實例化一個對象時,就是在依賴他的具體類。接着上面的例子,如果在一個PizzaStore里面直接創建很多對象時,他們的依賴關系是這樣的:
這里引出一個概念:依賴倒置。很清楚的代碼里減少具體類的依賴是一件好事。依賴倒置的定義是:要依賴抽象,不要依賴實現。這個原則說說明了:不能讓高層組建依賴底層組件,而且,不管是高層組件還是底層組件,他們都要依賴抽象。所謂的高層組件,是由其它底層組件定義其行為的類。例如。PizzaStore是個高層組件,因為他的行為是由比薩定義的:PizzaStore創建所有不同的比薩對象,准備、烘烤、切片、裝盒;而披薩本身屬於低層組件。
原則的應用
上圖展示的問題在於,它依賴每個比薩類型,因為它是在自己的orderPizza方法中,實例化這些具體類型的。雖然我們已經創建了一個抽象,就是Pizza,但是我們仍然在代碼中,實際的創建了具體的pizza,所以,這個抽象沒有什么影響力。如何在OrderPizza方法中,將這些實例化對象的代碼獨立出來,我們都知道,工廠方法剛好能排上用場。所以,應用工廠方法之后。類圖看起來就像這:
在應用了工廠方法模式之后(參考工廠方法模式),你將注意到,高層組件和底層組件都依賴了抽象(Pizza),要遵循依賴倒置原則,工廠方法並非唯一的技巧,但是,確實最有威力的技巧之一。
如何更好的遵循依賴倒置原則
變量不可以持有具體類的引用。如果使用new,就會持有具體類的引用,就會對具體的類產生依賴。可以改用工廠來避免這種做法。
不要讓類派生自具體的類。如果派生自具體類,那你就會依賴具體類。請派生自一個抽象。
不要覆蓋基類中已經實現的方法。基類中已實現的方法,應該由所有的子類共享。
上面三點只是說盡量去做到,並不是一定要做到。
抽象工廠模式
這個模式涉及的類有點兒多。。。所以決定線上UML類圖,然后根據類圖來一步一步的說明:
實際上單就拿抽象工廠來說,不是太難說明這個模式的含義,但是抽象工廠一般是和工廠方法模式配合使用的:
看圖:首先,NYPizzaStore還是繼承自PizzaStore

public abstract class PizzaStore { public Pizza OrderPizza(string pizzaType) { Pizza pizza = CreatePizza(pizzaType); pizza.Prepare(); pizza.Bake(); pizza.Cut(); pizza.Box(); return pizza; } public abstract Pizza CreatePizza(string pizzaType); }
PizzaStroe依賴一個抽象的Pizza,而NYPizzaStore繼承了PizzaStore,覆寫了CreatePIzza:

public class NyPizzaStore:PizzaStore { public override Pizza CreatePizza(string pizzaType) { Pizza pizza=null; var indigredientFactory=new NyPizzaIngredientFactory(); if (pizzaType=="Cheese") { pizza = new CheesePizza(indigredientFactory); } else if (pizzaType=="Clam") { pizza=new ClamPizza(indigredientFactory); } return pizza; } }
實現的這個NYPizzaStore的耦合比較高,因為他在倆面new了三個對象:一個抽象工廠NyPizzaIngredientFactory,兩個具體的披薩CheesePizza和ClamPizza。但是因為這個部分應該是不易變的部分,以后也不會進行修改了(如果不是這樣的話那這個類還得繼續抽象不是么?)此外,NYPizzaStore還針對一個Pizza抽象進行了編程,Pizza的代碼如下:

public abstract class Pizza { public string Name { get; set; } public Dough Dough { get; set; } public Sauce Sauce { get; set; } public Veggie[] Veggies { get; set; } public Repperoni Repperoni { get; set; } public Clams Clams { get; set; } public Cheese Cheese { get; set; } public abstract void Prepare(); public void Bake() { Console.WriteLine("bake..."); } public void Cut() { Console.WriteLine("Cut..."); } public void Box() { Console.WriteLine("Box..."); } public override string ToString() { return this.Name; } }
Pizza里面依賴了很多的抽象,也就是說Pizza是針對抽象在編程,具體是一個什么披薩,應該交給Pizza的子類來進行詳細的描述,此外,看到Pizza里面還有一個抽象的Prepare方法,由此判斷出來Pizza類也實現了工廠方法模式。看一下Pizza的具體實現類(其中一個):

public class CheesePizza : Pizza { private IPizzaIngredientFactory _factory; public CheesePizza(IPizzaIngredientFactory factory) { _factory = factory; } public override void Prepare() { Console.WriteLine($"Preparing {Name}"); Dough = _factory.CreateDough(); Sauce = _factory.CreateSauce(); Cheese = _factory.CreateCheese(); } }
CheesePizaa針對一個抽象的IPizzaIngredientFactory進行編程,這種利用組合的方式避免了繼承帶來的靜態的編譯所造成的不便,使得代碼可以在運行時體現出更靈活和可擴展的特性。IPizzaIngredientFactory是一個抽象工廠的接口,抽象工廠又叫做原料工廠,就是說生產產品所需要的各種原料都可以從抽象工廠來獲取:

public class NyPizzaIngredientFactory : IPizzaIngredientFactory { public Dough CreateDough() { return new ThinCruseDough(); } public Sauce CreateSauce() { return new MarinaraSauce(); } public Cheese CreateCheese() { return new ReggianoChesse(); } public Veggie[] CreateVeggies() { return new Veggie[] { new Garlic(), new Mushroom(), new Onion(), new RedPepper(), }; } public Repperoni CreateRepperoni() { return new SlicedRepperoni(); } public Clams CreateClams() { return new FreshClams(); } }
下面給出抽象工廠的定義:提供一個接口,用於創建相關或依賴對象的家族,而不需要明確指定具體類。在這個示例中,這個接口指的就是IPizzaInfredientFactory。
最重要的結論是,抽象工廠並不是單獨使用的,而是在工廠方法模式中進行擴展使用的。有的時候,我們要創建一些相關聯的或依賴的一族對象,這個時候可以把創建這些對象用抽象工廠來實現,而不是用工廠方法模式來實現,如果用工廠方法模式來實現,一個抽象產品對應一個抽象工廠,那樣的情況叫做“類型爆炸”。
最最重要的是:工廠方法模式遵循控制反轉的設計原則,並在此基礎上定義了一個框架,在框架中,如果又必要的話,將抽象工廠的邏輯放到這個框架中的適當位置上。所以,工廠方法模式和抽象工廠模式一般是配合使用的。抽象工廠的作用就是防止"類型爆炸”。
要點:
所有工廠都是用來封裝對象的創建。
簡單工廠提供給我們更多的是一種想法,讓我們萌生了將系統做一些分隔的想法,比如將對象的使用和實現進行解耦,等等。
工廠方法使用的是繼承和多態,吧對象的創建委托給子類。
抽象工廠使用的是對象的組合。對象的創建被實現在工廠接口所暴露的方法中來,比如Pizza類就實現工廠方法模式,里面的Prepare就是一個工廠方法。
所有工廠模式都是通過減少應用程序和類之間的依賴促進耦合。
依賴倒置原則,指導我們要盡量避免依賴具體,而要依賴抽象。
工廠是很有威力的技巧,幫我們盡量針對接口編程,而不是針對具體。