工廠模式有簡單工廠模式,工廠方法模式和抽象工廠模式幾種形態。其中簡單工廠模式和工廠方法模式已經在前面作過介紹。在這里,我們來介紹抽象工廠模式。
抽象工廠模式是所有形態的工廠模式中最為抽象和最具廣泛性的一種形態。
抽象工廠模式的定義
抽象工廠模式是工廠方法模式的進一步擴廣化和抽象化。我們給出抽象工廠模式的類圖定義如下。
 圖1. 抽象工廠模式的類圖定義 |
從上圖可以看出,簡單工廠模式涉及到以下的角色
抽象工廠(AbstractFactory)類或接口
擔任這個角色的是工廠方法模式的核心,它是與應用程序無關的。任何在模式中創立對象的工廠類必須實現這個接口,或繼承這個類。
實工廠類 (Conrete Factory)
擔任這個角色的是與應用程序緊密相關的,直接在應用程序調用下,創立產品實例的那樣一些類。
抽象產品 (Abstract Product)
擔任這個角色的類是工廠方法模式所創立的對象的父類,或它們共同擁有的接口。
實產品 (Concrete Product)
擔任這個角色的類是工廠方法模式所創立的任何對象所屬的類。
怎么這個類圖和工廠方法模式的類圖看起來是一樣的?
是的,圖是一樣的,但是含義有很大的不同。必須指出,在抽象工廠模式中,抽象產品 (AbstractProduct) 可能是一個或多個,從而構成一個或多個產品族(Product Family)。 在只有一個產品族的情況下,抽象工廠模式實際上退化到工廠方法模式。在上面的類圖中,只給出了一個產品族,相當于位圖中的一個點,而完整的位圖應當是三維的,如下圖。
 圖2. 抽象工廠模式的位圖 |
從位圖可以清楚地看到,與紙面垂直的數軸,即第三維軸,是代表產品族的數軸。上面的位圖中展示的是有兩個產品族,族A和族B的情形。
在只有一個產品族時,第三維就坍縮掉,位圖也就只剩下兩維。這時抽象工廠模式就退化得與工廠方法模式一模一樣。
在什么情形下應當使用抽象工廠模式
在以下情況下,應當考慮使用抽象工廠模式。
首先,一個系統應當不依賴于產品類實例被創立,組成,和表示的細節。這對于所有形態的工廠模式都是重要的。
其次,這個系統的產品有多于一個的產品族。
第三,同屬于同一個產品族的產品是設計成在一起使用的。這一約束必須得在系統的設計中體現出來。
最后,不同的產品以一系列的接口的面貌出現,從而使系統不依賴于接口實現的細節。
其中第二丶第三個條件是我們選用抽象工廠模式而非其它形態的工廠模式的關鍵性條件。
抽象工廠模式在小花果園系統中的實現
現在,我們在佛羅里達的渡假小屋修整好啦。接下來,一項重要而光榮的工作,就是開發小屋后面的小花園。這意味著,我們有兩處小花園需要照料,一處在北方地區,另一處在亞熱帶地區。抽象工廠模式正好適用于我們的情況。
 圖3. 抽象工廠模式應用于小花果園系統中。三種不同的背景顏色可以區分工廠類,蔬菜類(第一產品族),和水果類的類圖(第二產品族) |
兩處花園就相當于兩個產品族。顯然,給北方花園的植物是要種植在一起的,給南方花園的植物是要另種植在一起的。這種分別應當體現在系統的設計上面。這就滿足了應當使用抽象工廠模式的第二和第三個條件。
package com.javapatterns.abstractfactory;
public interface Gardener {} |
代碼清單1. 接口 Gardener。
package com.javapatterns.abstractfactory;
public class NorthenGardener implements Gardener { public VeggieIF createVeggie(String name) { return new NorthernVeggie(name); }
public FruitIF createFruit(String name) { return new NorthernFruit(name); }
} |
代碼清單2. 實工廠類 NorthenGardener。
package com.javapatterns.abstractfactory;
public class TropicalGardener implements Gardener { public VeggieIF createVeggie(String name) { return new TropicalVeggie(name); }
public FruitIF createFruit(String name) { return new TopicalFruit(name); }
} |
代碼清單3. 實工廠類 TropicalGardener。
package com.javapatterns.abstractfactory;
public interface VeggieIF {} |
代碼清單4. 接口 VeggieIF。
package com.javapatterns.abstractfactory;
public class NorthernVeggie implements VeggieIF { public NorthernVeggie(String name) { this.name = name; }
public String getName(){ return name; }
public void setName(String name){ this.name = name; }
private String name; } |
代碼清單5. 實產品類 NorthernVeggie。實產品類 NorthernFruit 與此極為類似,故略去。
package com.javapatterns.abstractfactory;
public class TropicalVeggie implements VeggieIF { public TropicalVeggie(String name) { this.name = name;}
public String getName(){ return name; }
public void setName(String name){ this.name = name; }
private String name; } |
代碼清單6. 實產品類 TropicalVeggie。實產品類 TropicalFruit 與此極為類似,故略去。
筆者對植物的了解有限,為免遺笑大方,在上面的系統里采用了簡化處理。沒有給出高緯度和低緯度的水果類或蔬菜類的具體名稱。
抽象工廠模式的另一個例子
這個例子講的是微型計算機的生產。產品族有兩個,PC(IBM系列)和Mac(MacIntosh系列)。顯然,我們應該使用抽象工廠模式,而不是工廠方法模式,因為后者適合于處理只有一個產品族的情形。
 圖4. 抽象工廠模式應用于微型計算機生產系統中。兩種不同的背景顏色可以區分兩類產品族,及其對應的實工廠類 |
關于模式的實現
在抽象實現工廠模式時,有下面一些值得注意的技巧。
第一丶實工廠類可以設計成單態類。很顯然,在小花果園系統中,我們只需要 NorthenGardener 和TropicalGardener 的一個實例就可以了。關于單態類的知識,請見<爪哇語言單態類創立性模式>。
第二丶在實現抽象工廠模式時,產品類往往分屬多于一個的產品族,而針對每一族,都需要一個實工廠類。在很多情況下,幾個實工廠類都彼此相象,只有些微的差別。
這時,筆者建議使用原始模型(Prototype)模式。這一模式會在以后介紹,屆時作者會進一步闡述這一點。
第三丶設計更加靈活的實工廠。以微型計算機生產系統為例,PCProducer 是一個實工廠類,它的不靈活之處在于,每一種產品都有一個工廠方法。CPU 有createCPU(),RAM 有createRAM(),等等。如果一個已有的系統需要擴充,比如增加硬盤這一新產品,我們就需要增加一系列的接口 (createHD())丶類(HD, PCHD, MacHD)和方法。這似乎不很理想。
一個解決的辦法是,把createCPU(),createRAM(), createHD()這幾個方法合并為一個createPart(String type)方法。這個合并后的方法返還一個Part接口。所有的產品都要實現這一接口,而CPU,RAM,和HD接口則不再需要了。每一個實產品都需要有一個屬性,表明它們的種類是CPU,RAM,和HD。
這樣做的結果是,數據類型的豐富結構被扁平化了。客戶端拿到的永遠是一個Part接口。這對客戶端而言不很安全。
第四丶抽象工廠類可以配備靜態方法,以返還實工廠。設計的方法有兩種。
一種是以一個靜態方法,按照參量的值,返回所對應的實工廠。靜態方法的數據類型是抽象方法類。
另一種是以每一個實工廠類都配備一個靜態方法,其數據類型是該實工廠類。
問答題
第1題。如上面的討論,抽象工廠類可以配備一個靜態方法,按照參量的值,返回所對應的實工廠。請把微型計算機生產系統的抽象工廠類按照這一方案改造,給出UML類圖和源代碼。
第2題。如上面的討論,抽象工廠類可以配備一系列靜態方法對應一系列的實工廠。請把微型計算機生產系統的抽象工廠類按照這一方案改造,給出UML類圖和源代碼。
第3題。如上面的討論,實工廠類可以設計成單態類。請在第1題的基礎上把微型計算機生產系統的實工廠類按照這一方案改造,給出UML類圖和源代碼。
問答題答案
第1題。微型計算機生產系統的抽象工廠原本是接口,現在需要改造成抽象類。
 圖5. 三種不同的背景顏色可以區分抽象工廠類,兩類產品族,及其對應的實工廠類。ComputerProducer 類圖中類名為斜體表明該類是抽象的,而getProducer()的下劃線表明該方法是靜態的 |
package com.javapatterns.abstractfactory.exercise1;
public class ComputerProducer { public static ComputerProducer getProducer(String which) { if (which.equalsIgnoreCase("PC")) { return new PCProducer(); } else (which.equalsIgnoreCase("Mac")) { return new MacProducer(); } } } |
代碼清單7. 抽象類 ComputerProducer 的方法 getProducer(String which)。
第2題。略。
第3題。本題答案是在第1題基礎之上的。
 圖6. 三種不同的背景顏色可以區分抽象工廠類,兩類產品族,及其對應的實工廠類。ComputerProducer 類圖中類名為斜體表明該類是抽象的,而getProducer()的下劃線表明該方法是靜態的。MacProducer 和 PCProducer 的構造子是私有的,因此這兩個類必須自己將自己實例化。 |
package com.javapatterns.abstractfactory.exercise3;
abstract public class ComputerProducer { public static ComputerProducer getProducer(String which) { if (which.equalsIgnoreCase("PC")) { return PCProducer.getInstance(); } else (which.equalsIgnoreCase("Mac")) { return MacProducer.getInstance(); } } } |
代碼清單8.抽象工廠類ComputerProducer。
package com.javapatterns.abstractfactory.exercise3;
public class MacProducer extends ComputerProducer { private MacProducer() { }
public CPU createCPU() {}
public RAM createRAM() {}
private static final m_MacProducer = new MacProducer();
} |
代碼清單9. 實工廠類 MacProducer 是單態類。讀過筆者<單態創立性模式>一節的讀者應當知道,這里使用的單態類實現方法是餓漢式方法。
package com.javapatterns.abstractfactory.exercise3;
public class PCProducer extends ComputerProducer { private PCProducer() { }
public CPU createCPU() {}
public RAM createRAM() {}
private static final m_PCProducer = new PCProducer(); } |
代碼清單10. 實工廠類 PCProducer 是單態類,使用的單態類實現方法是餓漢式方法。
各產品類沒有變化,因此不在此重復。