設計模式(Design pattern)是一套被反複使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人了解、保證代碼可靠性。 毫無疑問,設計模式于己于他人于系統都是多赢的;設計模式使代碼編制真正工程化;設計模式是軟體工程的基石脈絡,如同大廈的結構一樣。
設計模式分為三種類型,共23種。
建立型模式(5):單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式。
結構型模式(7):擴充卡模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
行為型模式(11):(父子類)政策模式、模版方法模式,(兩個類)觀察者模式、疊代器模式、職責鍊模式、指令模式,(類的狀态)狀态模式、備忘錄模式,(中間類) 通路者模式、中介者模式、解釋器模式。
工廠方法模式通過引入工廠等級結構,解決了簡單工廠模式中工廠類職責太重的問題,但由于工廠方法模式中的每個工廠隻生産一類産品,可能會導緻系統中存在大量的工廠類,勢必會增加系統的開銷。此時,我們可以考慮将一些相關的産品組成一個“産品族”,由同一個工廠來統一生産,這就是我們本文将要學習的抽象工廠模式的基本思想。
一.概述
在工廠方法模式中具體工廠負責生産具體的産品,每一個具體工廠對應一種具體産品,工廠方法具有唯一性,一般情況下,一個具體工廠中隻有一個或者一組重載的工廠方法。但是有時候我們希望一個工廠可以提供多個産品對象,而不是單一的産品對象,如一個電器工廠,它可以生産電視機、電冰箱、空調等多種電器,而不是隻生産某一種電器。為了更好地了解抽象工廠模式,我們先引入兩個概念:
(1) 産品等級結構:産品等級結構即産品的繼承結構,如一個抽象類是電視機,其子類有海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構成了一個産品等級結構,抽象電視機是父類,而具體品牌的電視機是其子類。
(2) 産品族:在抽象工廠模式中,産品族是指由同一個工廠生産的,位于不同産品等級結構中的一組産品,如海爾電器工廠生産的海爾電視機、海爾電冰箱,海爾電視機位于電視機産品等級結構中,海爾電冰箱位于電冰箱産品等級結構中,海爾電視機、海爾電冰箱構成了一個産品族。
産品等級結構與産品族示意圖如圖所示:

在圖中,不同顔色的多個正方形、圓形和橢圓形分别構成了三個不同的産品等級結構,而相同顔色的正方形、圓形和橢圓形構成了一個産品族,每一個形狀對象都位于某個産品族,并屬于某個産品等級結構。圖3中一共有五個産品族,分屬于三個不同的産品等級結構。我們隻要指明一個産品所處的産品族以及它所屬的等級結構,就可以唯一确定這個産品。
當系統所提供的工廠生産的具體産品并不是一個簡單的對象,而是多個位于不同産品等級結構、屬于不同類型的具體産品時就可以使用抽象工廠模式。抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形式。抽象工廠模式與工廠方法模式最大的差別在于,工廠方法模式針對的是一個産品等級結構,而抽象工廠模式需要面對多個産品等級結構,一個工廠等級結構可以負責多個不同産品等級結構中的産品對象的建立。當一個工廠等級結構可以建立出分屬于不同産品等級結構的一個産品族中的所有對象時,抽象工廠模式比工廠方法模式更為簡單、更有效率。抽象工廠模式示意圖如圖所示:
在圖中,每一個具體工廠可以生産屬于一個産品族的所有産品,例如生産顔色相同的正方形、圓形和橢圓形,所生産的産品又位于不同的産品等級結構中。如果使用工廠方法模式,圖所示結構需要提供15個具體工廠,而使用抽象工廠模式隻需要提供5個具體工廠,極大減少了系統中類的個數。
定義
抽象工廠模式為建立一組對象提供了一種解決方案。與工廠方法模式相比,抽象工廠模式中的具體工廠不隻是建立一種産品,它負責建立一族産品。
抽象工廠模式(Abstract Factory Pattern):提供一個建立一系列相關或互相依賴對象的接口,而無須指定它們具體的類。抽象工廠模式又稱為Kit模式,它是一種對象建立型模式。
Abstract Factory Pattern:Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
結構
在抽象工廠模式中,每一個具體工廠都提供了多個工廠方法用于産生多種不同類型的産品,這些産品構成了一個産品族,抽象工廠模式結構如圖所示:
在抽象工廠模式結構圖中包含如下幾個角色:
- AbstractFactory(抽象工廠):它聲明了一組用于建立一族産品的方法,每一個方法對應一種産品。
- ConcreteFactory(具體工廠):它實作了在抽象工廠中聲明的建立産品的方法,生成一組具體産品,這些産品構成了一個産品族,每一個産品都位于某個産品等級結構中。
- AbstractProduct(抽象産品):它為每種産品聲明接口,在抽象産品中聲明了産品所具有的業務方法。
- ConcreteProduct(具體産品):它定義具體工廠生産的具體産品對象,實作抽象産品接口中聲明的業務方法。
實作
在抽象工廠中聲明了多個工廠方法,用于建立不同類型的産品,抽象工廠可以是接口,也可以是抽象類或者具體類,其典型代碼如下所示:
abstract class AbstractFactory {
public abstract AbstractProductA createProductA(); //工廠方法一
public abstract AbstractProductB createProductB(); //工廠方法二
……
}
具體工廠實作了抽象工廠,每一個具體的工廠方法可以傳回一個特定的産品對象,而同一個具體工廠所建立的産品對象構成了一個産品族。對于每一個具體工廠類,其典型代碼如下所示:
class ConcreteFactory1 extends AbstractFactory {
//工廠方法一
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
//工廠方法二
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
……
}
與工廠方法模式一樣,抽象工廠模式也可為每一種産品提供一組重載的工廠方法,以不同的方式對産品對象進行建立。
二.界面皮膚庫的設計
Sunny軟體公司欲開發一套界面皮膚庫,可以對Java桌面軟體進行界面美化。為了保護版權,該皮膚庫源代碼不打算公開,而隻向使用者提供已打包為jar檔案的class位元組碼檔案。使用者在使用時可以通過菜單來選擇皮膚,不同的皮膚将提供視覺效果不同的按鈕、文本框、組合框等界面元素,其結構示意圖如圖所示:
Sunny公司開發人員使用抽象工廠模式來重構界面皮膚庫的設計,其基本結構如圖所示:
在圖中,SkinFactory接口充當抽象工廠,其子類SpringSkinFactory和SummerSkinFactory充當具體工廠,接口Button、TextField和ComboBox充當抽象産品,其子類SpringButton、SpringTextField、SpringComboBox和SummerButton、SummerTextField、SummerComboBox充當具體産品。完整代碼如下所示:
// 按鈕接口:抽象産品
interface Button {
public void display();
}
// Spring按鈕類:具體産品
class SpringButton implements Button {
public void display() {
System.out.println("顯示淺綠色按鈕。");
}
}
// Summer按鈕類:具體産品
class SummerButton implements Button {
public void display() {
System.out.println("顯示淺藍色按鈕。");
}
}
// 文本框接口:抽象産品
interface TextField {
public void display();
}
// Spring文本框類:具體産品
class SpringTextField implements TextField {
public void display() {
System.out.println("顯示綠色邊框文本框。");
}
}
// Summer文本框類:具體産品
class SummerTextField implements TextField {
public void display() {
System.out.println("顯示藍色邊框文本框。");
}
}
// 組合框接口:抽象産品
interface ComboBox {
public void display();
}
// Spring組合框類:具體産品
class SpringComboBox implements ComboBox {
public void display() {
System.out.println("顯示綠色邊框組合框。");
}
}
// Summer組合框類:具體産品
class SummerComboBox implements ComboBox {
public void display() {
System.out.println("顯示藍色邊框組合框。");
}
}
// 界面皮膚工廠接口:抽象工廠
interface SkinFactory {
public Button createButton();
public TextField createTextField();
public ComboBox createComboBox();
}
// Spring皮膚工廠:具體工廠
class SpringSkinFactory implements SkinFactory {
public Button createButton() {
return new SpringButton();
}
public TextField createTextField() {
return new SpringTextField();
}
public ComboBox createComboBox() {
return new SpringComboBox();
}
}
// Summer皮膚工廠:具體工廠
class SummerSkinFactory implements SkinFactory {
public Button createButton() {
return new SummerButton();
}
public TextField createTextField() {
return new SummerTextField();
}
public ComboBox createComboBox() {
return new SummerComboBox();
}
}
用戶端測試代碼:
public class Client {
public static void main(String[] args) throws Exception {
//使用抽象層定義
SkinFactory factory = new SpringSkinFactory();
Button bt = factory.createButton();
TextField tf = factory.createTextField();
ComboBox cb = factory.createComboBox();
bt.display();
tf.display();
cb.display();
}
}
輸出結果如下:
顯示淺綠色按鈕。
顯示綠色邊框文本框。
顯示綠色邊框組合框。
三.“開閉原則”的傾斜性
Sunny公司使用抽象工廠模式設計了界面皮膚庫,該皮膚庫可以較為友善地增加新的皮膚,但是現在遇到一個非常嚴重的問題:由于設計時考慮不全面,忘記為單選按鈕(RadioButton)提供不同皮膚的風格化顯示,導緻無論選擇哪種皮膚,單選按鈕都顯得那麼“格格不入”。Sunny公司的設計人員決定向系統中增加單選按鈕,但是發現原有系統居然不能夠在符合“開閉原則”的前提下增加新的元件,原因是抽象工廠SkinFactory中根本沒有提供建立單選按鈕的方法,如果需要增加單選按鈕,首先需要修改抽象工廠接口SkinFactory,在其中新增聲明建立單選按鈕的方法,然後逐個修改具體工廠類,增加相應方法以實作在不同的皮膚中建立單選按鈕,此外還需要修改用戶端,否則單選按鈕無法應用于現有系統。
怎麼辦?答案是抽象工廠模式無法解決該問題,這也是抽象工廠模式最大的缺點。在抽象工廠模式中,增加新的産品族很友善,但是增加新的産品等級結構很麻煩,抽象工廠模式的這種性質稱為“開閉原則”的傾斜性。“開閉原則”要求系統對擴充開放,對修改封閉,通過擴充達到增強其功能的目的,對于涉及到多個産品族與多個産品等級結構的系統,其功能增強包括兩方面:
(1) 增加産品族:對于增加新的産品族,抽象工廠模式很好地支援了“開閉原則”,隻需要增加具體産品并對應增加一個新的具體工廠,對已有代碼無須做任何修改。
(2) 增加新的産品等級結構:對于增加新的産品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生産新産品的方法,違背了“開閉原則”。
正因為抽象工廠模式存在“開閉原則”的傾斜性,它以一種傾斜的方式來滿足“開閉原則”,為增加新産品族提供友善,但不能為增加新産品結構提供這樣的友善,是以要求設計人員在設計之初就能夠全面考慮,不會在設計完成之後向系統中增加新的産品等級結構,也不會删除已有的産品等級結構,否則将會導緻系統出現較大的修改,為後續維護工作帶來諸多麻煩。
四.總結
抽象工廠模式是工廠方法模式的進一步延伸,由于它提供了功能更為強大的工廠類并且具備較好的可擴充性,在軟體開發中得以廣泛應用,尤其是在一些架構和API類庫的設計中,例如在Java語言的AWT(抽象視窗工具包)中就使用了抽象工廠模式,它使用抽象工廠模式來實作在不同的作業系統中應用程式呈現與所在作業系統一緻的外觀界面。抽象工廠模式也是在軟體開發中最常用的設計模式之一。
1. 主要優點
抽象工廠模式的主要優點如下:
(1) 抽象工廠模式隔離了具體類的生成,使得客戶并不需要知道什麼被建立。由于這種隔離,更換一個具體工廠就變得相對容易,所有的具體工廠都實作了抽象工廠中定義的那些公共接口,是以隻需改變具體工廠的執行個體,就可以在某種程度上改變整個軟體系統的行為。
(2) 當一個産品族中的多個對象被設計成一起工作時,它能夠保證用戶端始終隻使用同一個産品族中的對象。
(3) 增加新的産品族很友善,無須修改已有系統,符合“開閉原則”。
2. 主要缺點
抽象工廠模式的主要缺點如下:
增加新的産品等級結構麻煩,需要對原有系統進行較大的修改,甚至需要修改抽象層代碼,這顯然會帶來較大的不便,違背了“開閉原則”。
3. 适用場景
在以下情況下可以考慮使用抽象工廠模式:
(1) 一個系統不應當依賴于産品類執行個體如何被建立、組合和表達的細節,這對于所有類型的工廠模式都是很重要的,使用者無須關心對象的建立過程,将對象的建立和使用解耦。
(2) 系統中有多于一個的産品族,而每次隻使用其中某一産品族。可以通過配置檔案等方式來使得使用者可以動态改變産品族,也可以很友善地增加新的産品族。
(3) 屬于同一個産品族的産品将在一起使用,這一限制必須在系統的設計中展現出來。同一個産品族中的産品可以是沒有任何關系的對象,但是它們都具有一些共同的限制,如同一作業系統下的按鈕和文本框,按鈕與文本框之間沒有直接關系,但它們都是屬于某一作業系統的,此時具有一個共同的限制條件:作業系統的類型。
(4) 産品等級結構穩定,設計完成之後,不會向系統中增加新的産品等級結構或者删除已有的産品等級結構。
練習
Sunny軟體公司欲推出一款新的手機遊戲軟體,該軟體能夠支援Symbian、Android和Windows Mobile等多個智能手機作業系統平台,針對不同的手機作業系統,該遊戲軟體提供了不同的遊戲操作控制(OperationController)類和遊戲界面控制(InterfaceController)類,并提供相應的工廠類來封裝這些類的初始化過程。軟體要求具有較好的擴充性以支援新的作業系統平台,為了滿足上述需求,試采用抽象工廠模式對其進行設計。
以上内容來自于博文: JAVA設計模式(02):建立型-抽象工廠模式(Abstract Factory)
【作者:劉偉(Sunny) http://blog.csdn.net/lovelion】
參考電子書下載下傳:設計模式的藝術–軟體開發人員内功修煉之道_劉偉(2013年).pdf
《道德經》第二十三章:
希言自然。故飄風不終朝,驟雨不終日,孰為此者?天地。天地尚不能久,而況于人乎?故從事于道者同于道;德者同于德;失者同于失。同于道者,道亦樂得之;同于德者,德亦樂得之;同于失者,失亦樂得之。信不足焉,有不信焉!
譯文:不言政令不擾民是合乎于自然的。狂風刮不了一個早晨,暴雨下不了一整天。誰使它這樣的呢?天地。天地的狂暴尚且不能長久,更何況是人呢?是以,從事于道的就同于道,從事于德的就同于德,從事于失的人就同于失。同于道的人,道也樂于得到他;同于德的人,德也樂于得到他;同于失的人,失也樂于得到他。統治者的誠信不足,就會有人不信任。