第5章 抽象工廠模式
5.1 產品等級結構與產品族
-
產品等級結構:產品等級結構即產品的繼承結構。
例如一個抽象類是電視機,其子類包括海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構成了一個產品等級結構,抽象電視機是父類,而具體品牌的電視機是其子類。 -
產品族:產品族是指由同一個工廠生產的位於不同產品等級結構中的一組產品。
例如海爾電器工廠生產的海爾電視機、海爾冰箱、海爾電視機位於電視機產品等級結構中,海爾冰箱位於冰箱產品等級結構中,海爾電視機、海爾冰箱構成了一個產品族。
5.2 抽象工廠模式概述
當系統所提供的工廠生產的具體產品並不是一個簡單的物件,而是多個位於不同產品等級結構、屬於不同型別的具體產品時就可以使用抽象工廠模式。
抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形式。
抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構;而抽象工廠模式需要面對多個產品等級結構,一個工廠等級結構可以負責多個不同產品等級結構中的產品物件的建立。當一個工廠等級結構可以創建出分屬於不同產品等級結構的一個產品族中的所有物件時,抽象工廠模式比工廠方法模式更為簡單、更有效率。
抽象工程模式(Abstract Factory Pattern)
5.3 抽象工廠模式結構與實現
5.3.1 抽象工廠模式結構
- AbstractFactory(抽象工廠):它聲明瞭一組用於建立一族產品的方法,每一個方法對應一種產品。
- ConcreteFactory(具體工廠):它實現了在抽象工廠中宣告的建立產品的方法,生成一組具體產品,這些產品構成了一個產品族,每一個產品都位於某個產品等級結構中。
- AbstractProduct(抽象產品):它為每種產品宣告介面,在抽象產品中聲明瞭產品所具有的業務方法。
- ConcreteProduct(具體產品):它定義具體工廠生產的具體產品物件,實現抽象產品介面中宣告的業務方法。
5.3.2 抽象工廠模式實現
Button:按鈕介面,充當抽象產品。
package designpatterns.abstracfactory;
public interface Button {
public void display();
}
SpringButton:Spring按鈕類,充當具體產品。
package designpatterns.abstracfactory; public class SpringButton implements Button { @Override public void display() { System.out.println("顯示淺綠色按鈕."); } }
SummerButton:Summer按鈕類,充當具體產品。
package designpatterns.abstracfactory;
public class SummerButton implements Button {
@Override
public void display() {
System.out.println("顯示淺藍色按鈕.");
}
}
TextField:文字框介面,充當抽象產品。
package designpatterns.abstracfactory;
public interface TextField {
public void display();
}
Spring TextField:Spring文字框類,充當具體產品。
package designpatterns.abstracfactory;
public class SpringTextField implements TextField {
@Override
public void display() {
System.out.println("顯示綠色邊框文字框.");
}
}
SummerTextField:Summer文字框類,充當具體產品。
package designpatterns.abstracfactory;
public class SummerTextField implements TextField {
@Override
public void display() {
System.out.println("顯示藍色邊框文字框.");
}
}
ComboBox:組合框介面,充當抽象產品。
package designpatterns.abstracfactory;
public interface ComboBox {
public void display();
}
SpringComboBox:Spring 組合框類,充當具體產品。
package designpatterns.abstracfactory;
public class SpringComboBox implements ComboBox{
@Override
public void display() {
System.out.println("顯示綠色邊框組合框.");
}
}
SummerComboBox:Summer組合框類,充當具體產品。
package designpatterns.abstracfactory;
public class SummerConboBox implements ComboBox{
@Override
public void display() {
System.out.println("顯示藍色邊框組合框.");
}
}
SkinFactory:介面面板工廠介面,充當抽象工廠。
package designpatterns.abstracfactory;
public interface SkinFactory {
public Button createButton();
public TextField createTextField();
public ComboBox createComboBox();
}
SpringSkinFactory:Spring面板工廠,充當具體工廠。
package designpatterns.abstracfactory;
public class SpringSkinFactory implements SkinFactory {
@Override
public Button createButton() {
return new SpringButton();
}
@Override
public TextField createTextField() {
return new SpringTextField();
}
@Override
public ComboBox createComboBox() {
return new SpringComboBox();
}
}
SummerSkinFactory:Summer面板工廠,充當具體工廠。
package designpatterns.abstracfactory;
public class SummerSkinFactory implements SkinFactory {
@Override
public Button createButton() {
return new SummerButton();
}
@Override
public TextField createTextField() {
return new SummerTextField();
}
@Override
public ComboBox createComboBox() {
return new SpringComboBox();
}
}
配置檔案config.xml,在配置檔案中儲存了具體工廠類的類名。
<?xml version="1.0"?>
<config>
<className>designpatterns.abstracfactory.SpringSkinFactory</className>
</config>
XMLUtil:工具類。
package designpatterns.abstracfactory;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;
public class XMLUtil {
//該方法用於從XML配置檔案中提取具體類的類名,並返回一個例項物件
public static Object getBean() {
try {
//建立DOM文件物件
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//designpatterns//abstracfactory//config.xml"));
//獲取包含類名的文字結點
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = classNode.getNodeValue();
//通過類名生成例項物件並將其返回
Class c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Client:客戶端測試類。
package designpatterns.abstracfactory;
public class Client {
public static void main(String[] args) {
SkinFactory factory;
Button bt;
TextField tf;
ComboBox cb;
factory = (SkinFactory) XMLUtil.getBean();
bt = factory.createButton();
tf = factory.createTextField();
cb = factory.createComboBox();
bt.display();
tf.display();
cb.display();
}
}
結果及分析
5.5 開閉原則的傾斜性
在5.4節所設計的介面面板庫中可以較為方便地增加新型別的面板。
但是該設計方案存在一個非常嚴重的問題:如果在設計之初因為考慮不全面,忘記為某種型別的介面元件(以單選按鈕RadioButton為例)提供不同面板下的風格化顯示,那麼在往系統中增加單選按鈕時將發現非常麻煩,無法在滿足開閉原則的前提下增加單選按鈕,原因是抽象工廠SkinFactory中根本沒有提供建立單選按鈕的方法,如果需要增加單選按鈕,首先需要修改抽象工廠介面SkinFactory,在其中新增宣告建立單選按鈕的方法。然後逐個修改具體工廠類,增加相應方法以實現在不同的面板庫中建立單選按鈕,此外還需要修改客戶端,否則單選按鈕無法應用於現有系統。
抽象工廠模式無法解決此類問題,這也是抽象工廠模式的最大缺點所在。在抽象工廠模式中增加新的產品族很方便,但是增加新的產品等級結構很麻煩,抽象工廠模式的這種性質稱為開閉原則的傾斜性。
開閉原則要求系統對擴充套件開放,對修改關閉,通過擴充套件達到增強其功能的目的,對於涉及多個產品族與多個產品等級結構的系統,其功能增強包括兩個:
- 增加產品族:對於增加新的產品族,抽象工廠模式很好地支援了開閉原則,只需要增加具體產品並對應增加一個新的具體工廠,對已有程式碼無須做任何修改。
- 增加新的產品等級結構:對於增加新的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生產新產品的方法,違背了開閉原則。
5.6 抽象工廠模式優/缺點與適用環境
抽象工廠模式是工廠方法模式的進一步延伸,由於它提供了功能更為強大的工廠類且具備較好的可擴充套件性,在軟體開發中得以廣泛應用,尤其是在一些框架和API類庫的設計中。
例如在Java語言的AWT(抽象視窗工具包)中就使用了抽象工廠模式,它使用抽象工廠模式來實現在不同的作業系統中應用程式呈現與所在作業系統一致的外觀介面。
抽象衛工廠模式也是在軟體開發中最常用的設計模式之一。
5.6.1 抽象工廠模式優點
- 抽象工廠模式隔離了具體類的生成,使得客戶端並不需要知道什麼被建立。由於這種隔離,更換一個具體工廠就變得相對容易,所有的具體工廠都實現了抽象工廠中定義的那些公共介面,因此只需改變具體工廠的例項就可以在某種程度上改變整個軟體系統的行為。
- 當一個產品族中的多個物件被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的物件。
- 增加新的產品族很方便,無須修改已有系統,符合開閉原則。
5.6.2 抽象工廠缺點
增加新的產品等級結構麻煩,需要對原有系統進行較大的修改,甚至需要修改抽象層程式碼,這顯然會帶來較大的不便,違背了開閉原則。
5.6.3 抽象工廠適用環境
- 一個系統不應當依賴於產品類例項如何被建立、組合和表達的細節,這對於所有型別的工廠模式都是很重要的,使用者無須關心物件的建立過程,將物件的建立和使用解耦。
- 系統中有多於一個的產品族,而每次只使用其中某一產品族。可以通過配置檔案等方式來使使用者能夠動態改變產品族,也可以很方便地增加新的產品族。
- 屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。同一個產品族中的產品可以是沒有任何關係的物件,但是它們都具有一些共同的約束,如同一作業系統下的按鈕和文字框,按鈕與文字框之間沒有直接關係,但它們都是屬於某一作業系統的,此時具有一個共同的約束條件,即作業系統的型別。
- 產品等級結構穩定,在設計完成之後不會向系統中增加新的產品等級結構或者刪除已有的產品等級結構。