在工廠方法模式中,我們使用一個工廠創建一個產品,也就是說一個具體的工廠對應一個具體的產品。但是有時候我們需要一個工廠能夠提供多個產品對象,而不是單一的對象,這個時候我們就需要使用抽象工廠模式。
在講解抽象工廠模式之前,我們需要厘清兩個概念:
產品等級結構。產品的等級結構也就是產品的繼承結構。例如一個為空調的抽象類,它有海爾空調、格力空調、美的空調等一系列的子類,那么這個抽象類空調和他的子類就構成了一個產品等級結構。
產品族。產品族是在抽象工廠模式中的。在抽象工廠模式中,產品族是指由同一個工廠生產的,位於不同產品等級結構中的一組產品。比如,海爾工廠生產海爾空調。海爾冰箱,那么海爾空調則位於空調產品族中。
產品等級結構和產品族結構示意圖如下:

一、基本定義
抽象工廠模式提供一個接口,用於創建相關或者依賴對象的家族,而不需要明確指定具體類。
抽象工廠允許客戶端使用抽象的接口來創建一組相關的產品,而不需要關系實際產出的具體產品是什么。這樣一來,客戶就可以從具體的產品中被解耦。
二、模式結構
抽象工廠模式的UML結構圖如下:

模式結構說明。
AbstractFactory:抽象工廠。抽象工廠定義了一個接口,所有的具體工廠都必須實現此接口,這個接口包含了一組方法用來生產產品。
ConcreteFactory:具體工廠。具體工廠是用於生產不同產品族。要創建一個產品,客戶只需要使用其中一個工廠完全不需要實例化任何產品對象。
AbstractProduct:抽象產品。這是一個產品家族,每一個具體工廠都能夠生產一整組產品。
Product:具體產品。
三、模式實現
依然是披薩店。為了要保證每家加盟店都能夠生產高質量的披薩,防止使用劣質的原料,我們打算建造一家生產原料的工廠,並將原料運送到各家加盟店。但是加盟店都位於不同的區域,比如紐約、芝加哥。紐約使用一組原料,芝加哥使用另一種原料。在這里我們可以這樣理解,這些不同的區域組成了原料家族,每個區域實現了一個完整的原料家族。

首先創建一個原料工廠。該工廠為抽象工廠,負責創建所有的原料。
PizzaIngredientFactory.java
1 public interface PizzaIngredientFactory { 2 /* 3 * 在接口中,每個原料都有一個對應的方法創建該原料 4 */ 5 public Dough createDough(); 6 7 public Sauce createSauce(); 8 9 public Cheese createCheese(); 10 11 public Veggies[] createVeggies(); 12 13 public Pepperoni createPepperoni(); 14 15 public Clams createClams(); 16 }
原料工廠創建完成之后,需要創建具體的原料工廠。該具體工廠只需要繼承PizzaIngredientFactory,然后實現里面的方法即可。
紐約原料工廠:NYPizzaIngredientFactory.java。
1 public class NYPizzaIngredientFactory implements PizzaIngredientFactory{ 2 3 @Override 4 public Cheese createCheese() { 5 return new ReggianoCheese(); 6 } 7 8 @Override 9 public Clams createClams() { 10 return new FreshClams(); 11 } 12 13 @Override 14 public Dough createDough() { 15 return new ThinCrustDough(); 16 } 17 18 @Override 19 public Pepperoni createPepperoni() { 20 return new SlicedPepperoni(); 21 } 22 23 @Override 24 public Sauce createSauce() { 25 return new MarinaraSauce(); 26 } 27 28 @Override 29 public Veggies[] createVeggies() { 30 Veggies veggies[] = {new Garlic(),new Onion(),new Mushroom(),new RefPepper()}; 31 return veggies; 32 } 33 34 }
重新返回到披薩。在這個披薩類里面,我們需要使用原料,其他方法保持不變,將prepare()方法聲明為抽象,在這個方法中,我們需要收集披薩所需要的原料。
Pizza.java
1 public abstract class Pizza { 2 /* 3 * 每個披薩都持有一組在准備時會用到的原料 4 */ 5 String name; 6 Dough dough; 7 Sauce sauce; 8 Veggies veggies[]; 9 Cheese cheese; 10 Pepperoni pepperoni; 11 Clams clams; 12 13 /* 14 * prepare()方法聲明為抽象方法。在這個方法中,我們需要收集披薩所需要的原料,而這些原料都是來自原料工廠 15 */ 16 abstract void prepare(); 17 18 void bake(){ 19 System.out.println("Bake for 25 munites at 350"); 20 } 21 22 void cut(){ 23 System.out.println("Cutting the pizza into diagonal slices"); 24 } 25 26 void box(){ 27 System.out.println("Place pizza in official PizzaStore box"); 28 } 29 30 public String getName() { 31 return name; 32 } 33 34 public void setName(String name) { 35 this.name = name; 36 } 37 38 }
CheesePizza.java
1 public class CheesePizza extends Pizza{ 2 PizzaIngredientFactory ingredientFactory; 3 4 /* 5 * 要制作披薩必須要有制作披薩的原料,而這些原料是從原料工廠運來的 6 */ 7 public CheesePizza(PizzaIngredientFactory ingredientFactory){ 8 this.ingredientFactory = ingredientFactory; 9 prepare(); 10 } 11 12 /** 13 * 實現prepare方法 14 * prepare 方法一步一步地創建芝士比薩,每當需要原料時,就跟工廠要 15 */ 16 void prepare() { 17 System.out.println("Prepareing " + name); 18 dough = ingredientFactory.createDough(); 19 sauce = ingredientFactory.createSauce(); 20 cheese = ingredientFactory.createCheese(); 21 } 22 23 }
Pizza的代碼利用相關的工廠生產原料。所生產的原料依賴所使用的工廠,Pizza類根本不關心這些原料,它只需要知道如何制作披薩即可。這里,Pizza和區域原料之間被解耦。
ClamPizza.java
1 public class ClamPizza extends Pizza{ 2 3 PizzaIngredientFactory ingredientFactory; 4 5 public ClamPizza(PizzaIngredientFactory ingredientFactory){ 6 this.ingredientFactory = ingredientFactory; 7 } 8 9 @Override 10 void prepare() { 11 System.out.println("Prepare " + name); 12 dough = ingredientFactory.createDough(); 13 sauce = ingredientFactory.createSauce(); 14 cheese = ingredientFactory.createCheese(); 15 clams = ingredientFactory.createClams(); 16 } 17 18 }
做完披薩后,需要關注披薩店了。
在披薩店中,我們依然需要關注原料,當地的披薩店需要和本地的原料工廠關聯起來。
PizzaStore.java
1 public abstract class PizzaStore { 2 public Pizza orderPizza(String type){ 3 Pizza pizza; 4 pizza = createPizza(type); 5 6 pizza.prepare(); 7 pizza.bake(); 8 pizza.cut(); 9 pizza.box(); 10 11 return pizza; 12 } 13 14 /* 15 * 創建pizza的方法交給子類去實現 16 */ 17 abstract Pizza createPizza(String type); 18 }
紐約的披薩店:NYPizzaStore.java
1 public class NYPizzaStore extends PizzaStore{ 2 3 @Override 4 Pizza createPizza(String type) { 5 Pizza pizza = null; 6 //使用紐約的原料工廠 7 PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); 8 if("cheese".equals(type)){ 9 pizza = new CheesePizza(ingredientFactory); 10 pizza.setName("New York Style Cheese Pizza"); 11 } 12 else if("veggie".equals(type)){ 13 pizza = new VeggiePizza(ingredientFactory); 14 pizza.setName("New York Style Veggie Pizza"); 15 } 16 else if("clam".equals(type)){ 17 pizza = new ClamPizza(ingredientFactory); 18 pizza.setName("New York Style Clam Pizza"); 19 } 20 else if("pepperoni".equals(type)){ 21 pizza = new PepperoniPizza(ingredientFactory); 22 pizza.setName("New York Style Pepperoni Pizza"); 23 } 24 return pizza; 25 } 26 }
下圖是上面的UML結構圖。

其中PizzaIngredientFactory是抽象的披薩原料工廠接口,它定義了如何生產一個相關產品的家族。這個家族包含了所有制作披薩的原料。
NYPizzaIngredientFactory和ChicagoPizzaIngredientFactory是兩個具體披薩工廠類,他們負責生產相應的披薩原料。
NYPizzaStore是抽象工廠的客戶端。
四、模式優缺點
優點
1、 抽象工廠隔離了具體類的生成,是的客戶端不需要知道什么被創建。所有的具體工廠都實現了抽象工廠中定義的公共接口,因此只需要改變具體工廠的實例,就可以在某種程度上改變整個軟件系統的行為。
2、 當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的對象。
缺點
添加新的行為時比較麻煩。如果需要添加一個新產品族對象時,需要更改接口及其下所有子類,這必然會帶來很大的麻煩。
五、模式使用場景
1. 一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有類型的工廠模式都是重要的。
2.系統中有多於一個的產品族,而每次只使用其中某一產品族。
3. 屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。
4. 系統提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現。
六、總結
1、 抽象工廠模式中主要的優點在於具體類的隔離,是的客戶端不需要知道什么被創建了。其缺點在於增加新的等級產品結構比較復雜,需要修改接口及其所有子類。
