java之設計模式工廠三兄弟之抽象工廠模式

【學習難度:★★★★☆,使用頻率:★★★★★】 java

工廠方法模式經過引入工廠等級結構,解決了簡單工廠模式中工廠類職責過重的問題,但因爲工廠方法模式中的每一個工廠只生產一類產品,可能會致使系統中存在大量的工廠類,勢必會增長系統的開銷。此時,咱們能夠考慮將一些相關的產品組成一個「產品族」,由同一個工廠來統一輩子產,這就是咱們本文將要學習的抽象工廠模式的基本思想。android

 

1 界面皮膚庫的初始設計

 

       Sunny軟件公司欲開發一套界面皮膚庫,能夠對Java桌面軟件進行界面美化。爲了保護版權,該皮膚庫源代碼不打算公開,而只向用戶提供已打包爲jar文件的class字節碼文件。用戶在使用時能夠經過菜單來選擇皮膚,不一樣的皮膚將提供視覺效果不一樣的按鈕、文本框、組合框等界面元素,其結構示意圖如圖1所示:編程

圖1 界面皮膚庫結構示意圖設計模式

       該皮膚庫須要具有良好的靈活性和可擴展性,用戶能夠自由選擇不一樣的皮膚,開發人員能夠在不修改既有代碼的基礎上增長新的皮膚。框架

 

       Sunny軟件公司的開發人員針對上述要求,決定使用工廠方法模式進行系統的設計,爲了保證系統的靈活性和可擴展性,提供一系列具體工廠來建立按鈕、文本框、組合框等界面元素,客戶端針對抽象工廠編程,初始結構如圖2所示:dom

圖2 基於工廠方法模式的界面皮膚庫初始結構圖工具

       在圖2中,提供了大量工廠來建立具體的界面組件,能夠經過配置文件更換具體界面組件從而改變界面風格。可是,此設計方案存在以下問題:學習

       (1) 當須要增長新的皮膚時,雖然不要修改現有代碼,可是須要增長大量類,針對每個新增具體組件都須要增長一個具體工廠,類的個數成對增長,這無疑會致使系統愈來愈龐大,增長系統的維護成本和運行開銷;測試

       (2) 因爲同一種風格的具體界面組件一般要一塊兒顯示,所以須要爲每一個組件都選擇一個具體工廠,用戶在使用時必須逐個進行設置,若是某個具體工廠選擇失誤將會致使界面顯示混亂,雖然咱們能夠適當增長一些約束語句,但客戶端代碼和配置文件都較爲複雜。ui

       如何減小系統中類的個數並保證客戶端每次始終只使用某一種風格的具體界面組件?這是Sunny公司開發人員所面臨的兩個問題,顯然,工廠方法模式沒法解決這兩個問題,彆着急,本文所介紹的抽象工廠模式可讓這些問題迎刃而解。

2 產品等級結構與產品族

       在工廠方法模式中具體工廠負責生產具體的產品,每個具體工廠對應一種具體產品,工廠方法具備惟一性,通常狀況下,一個具體工廠中只有一個或者一組重載的工廠方法。可是有時候咱們但願一個工廠能夠提供多個產品對象,而不是單一的產品對象,如一個電器工廠,它能夠生產電視機、電冰箱、空調等多種電器,而不是隻生產某一種電器。爲了更好地理解抽象工廠模式,咱們先引入兩個概念:

       (1) 產品等級結構產品等級結構即產品的繼承結構,如一個抽象類是電視機,其子類有海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構成了一個產品等級結構,抽象電視機是父類,而具體品牌的電視機是其子類。

       (2) 產品族:在抽象工廠模式中,產品族是指由同一個工廠生產的,位於不一樣產品等級結構中的一組產品,如海爾電器工廠生產的海爾電視機、海爾電冰箱,海爾電視機位於電視機產品等級結構中,海爾電冰箱位於電冰箱產品等級結構中,海爾電視機、海爾電冰箱構成了一個產品族。

       產品等級結構與產品族示意圖如圖3所示:

圖3  產品族與產品等級結構示意圖

       在圖3中,不一樣顏色的多個正方形、圓形和橢圓形分別構成了三個不一樣的產品等級結構,而相同顏色的正方形、圓形和橢圓形構成了一個產品族,每個形狀對象都位於某個產品族,並屬於某個產品等級結構。圖3中一共有五個產品族,分屬於三個不一樣的產品等級結構。咱們只要指明一個產品所處的產品族以及它所屬的等級結構,就能夠惟一肯定這個產品。

       當系統所提供的工廠生產的具體產品並非一個簡單的對象,而是多個位於不一樣產品等級結構、屬於不一樣類型的具體產品時就可使用抽象工廠模式。抽象工廠模式是全部形式的工廠模式中最爲抽象和最具通常性的一種形式。抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式須要面對多個產品等級結構,一個工廠等級結構能夠負責多個不一樣產品等級結構中的產品對象的建立。當一個工廠等級結構能夠建立出分屬於不一樣產品等級結構的一個產品族中的全部對象時,抽象工廠模式比工廠方法模式更爲簡單、更有效率。抽象工廠模式示意圖如圖4所示:

圖4 抽象工廠模式示意圖

       在圖4中,每個具體工廠能夠生產屬於一個產品族的全部產品,例如生產顏色相同的正方形、圓形和橢圓形,所生產的產品又位於不一樣的產品等級結構中。若是使用工廠方法模式,圖4所示結構須要提供15個具體工廠,而使用抽象工廠模式只須要提供5個具體工廠,極大減小了系統中類的個數。

3 抽象工廠模式概述

       抽象工廠模式爲建立一組對象提供了一種解決方案。與工廠方法模式相比,抽象工廠模式中的具體工廠不僅是建立一種產品,它負責建立一族產品。抽象工廠模式定義以下:

 

       抽象工廠模式(Abstract Factory Pattern):提供一個建立一系列相關或相互依賴對象的接口,而無須指定它們具體的類。抽象工廠模式又稱爲Kit模式,它是一種對象建立型模式。

 

       在抽象工廠模式中,每個具體工廠都提供了多個工廠方法用於產生多種不一樣類型的產品,這些產品構成了一個產品族,抽象工廠模式結構如圖5所示:

圖5  抽象工廠模式結構圖

       在抽象工廠模式結構圖中包含以下幾個角色:

       ● 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();  
}  
  
……  
}  

4 完整解決方案

       Sunny公司開發人員使用抽象工廠模式來重構界面皮膚庫的設計,其基本結構如圖6所示:

圖6 界面皮膚庫結構圖

       在圖6中,SkinFactory接口充當抽象工廠,其子類SpringSkinFactory和SummerSkinFactory充當具體工廠,接口Button、TextField和ComboBox充當抽象產品,其子類SpringButton、SpringTextField、SpringComboBox和SummerButton、SummerTextField、SummerComboBox充當具體產品。完整代碼以下所示:

//在本實例中咱們對代碼進行了大量簡化,實際使用時,界面組件的初始化代碼較爲複雜,還須要使用JDK中一些已有類,爲了突出核心代碼,在此只提供框架代碼和演示輸出。  
//按鈕接口:抽象產品  
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();  
    }  
}  

 爲了讓系統具有良好的靈活性和可擴展性,咱們引入了工具類XMLUtil和配置文件,其中,XMLUtil類的代碼以下所示:

import javax.xml.parsers.*;  
import org.w3c.dom.*;  
import org.xml.sax.SAXException;  
import java.io.*;  
  
public class XMLUtil {  
//該方法用於從XML配置文件中提取具體類類名,並返回一個實例對象  
    public static Object getBean() {  
        try {  
            //建立文檔對象  
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();  
            DocumentBuilder builder = dFactory.newDocumentBuilder();  
            Document doc;                             
            doc = builder.parse(new File("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;  
        }  
    }  
}  

配置文件config.xml中存儲了具體工廠類的類名,代碼以下所示:

<?xml version="1.0"?>  
<config>  
    <className>SpringSkinFactory</className>  
</config>  

 編寫以下客戶端測試代碼:

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 「開閉原則」的傾斜性

       Sunny公司使用抽象工廠模式設計了界面皮膚庫,該皮膚庫能夠較爲方便地增長新的皮膚,可是如今遇到一個很是嚴重的問題:因爲設計時考慮不全面,忘記爲單選按鈕(RadioButton)提供不一樣皮膚的風格化顯示,致使不管選擇哪一種皮膚,單選按鈕都顯得那麼「格格不入」。Sunny公司的設計人員決定向系統中增長單選按鈕,可是發現原有系統竟然不可以在符合「開閉原則」的前提下增長新的組件,緣由是抽象工廠SkinFactory中根本沒有提供建立單選按鈕的方法,若是須要增長單選按鈕,首先須要修改抽象工廠接口SkinFactory,在其中新增聲明建立單選按鈕的方法,而後逐個修改具體工廠類,增長相應方法以實如今不一樣的皮膚中建立單選按鈕,此外還須要修改客戶端,不然單選按鈕沒法應用於現有系統。

       怎麼辦?答案是抽象工廠模式沒法解決該問題,這也是抽象工廠模式最大的缺點。在抽象工廠模式中,增長新的產品族很方便,可是增長新的產品等級結構很麻煩,抽象工廠模式的這種性質稱爲「開閉原則」的傾斜性。「開閉原則」要求系統對擴展開放,對修改封閉,經過擴展達到加強其功能的目的,對於涉及到多個產品族與多個產品等級結構的系統,其功能加強包括兩方面:

       (1) 增長產品族:對於增長新的產品族,抽象工廠模式很好地支持了「開閉原則」,只須要增長具體產品並對應增長一個新的具體工廠,對已有代碼無須作任何修改。

       (2) 增長新的產品等級結構:對於增長新的產品等級結構,須要修改全部的工廠角色,包括抽象工廠類,在全部的工廠類中都須要增長生產新產品的方法,違背了「開閉原則」。

       正由於抽象工廠模式存在「開閉原則」的傾斜性,它以一種傾斜的方式來知足「開閉原則」,爲增長新產品族提供方便,但不能爲增長新產品結構提供這樣的方便,所以要求設計人員在設計之初就可以全面考慮,不會在設計完成以後向系統中增長新的產品等級結構,也不會刪除已有的產品等級結構,不然將會致使系統出現較大的修改,爲後續維護工做帶來諸多麻煩。

 

6 抽象工廠模式總結

       抽象工廠模式是工廠方法模式的進一步延伸,因爲它提供了功能更爲強大的工廠類而且具有較好的可擴展性,在軟件開發中得以普遍應用,尤爲是在一些框架和API類庫的設計中,例如在Java語言的AWT(抽象窗口工具包)中就使用了抽象工廠模式,它使用抽象工廠模式來實如今不一樣的操做系統中應用程序呈現與所在操做系統一致的外觀界面。抽象工廠模式也是在軟件開發中最經常使用的設計模式之一。

 

       1. 主要優勢

       抽象工廠模式的主要優勢以下:

       (1) 抽象工廠模式隔離了具體類的生成,使得客戶並不須要知道什麼被建立。因爲這種隔離,更換一個具體工廠就變得相對容易,全部的具體工廠都實現了抽象工廠中定義的那些公共接口,所以只需改變具體工廠的實例,就能夠在某種程度上改變整個軟件系統的行爲。

       (2) 當一個產品族中的多個對象被設計成一塊兒工做時,它可以保證客戶端始終只使用同一個產品族中的對象。

       (3) 增長新的產品族很方便,無須修改已有系統,符合「開閉原則」。

 

       2. 主要缺點

       抽象工廠模式的主要缺點以下:

       增長新的產品等級結構麻煩,須要對原有系統進行較大的修改,甚至須要修改抽象層代碼,這顯然會帶來較大的不便,違背了「開閉原則」。

 

       3. 適用場景

       在如下狀況下能夠考慮使用抽象工廠模式:

       (1) 一個系統不該當依賴於產品類實例如何被建立、組合和表達的細節,這對於全部類型的工廠模式都是很重要的,用戶無須關心對象的建立過程,將對象的建立和使用解耦。

       (2) 系統中有多於一個的產品族,而每次只使用其中某一產品族。能夠經過配置文件等方式來使得用戶能夠動態改變產品族,也能夠很方便地增長新的產品族。

       (3) 屬於同一個產品族的產品將在一塊兒使用,這一約束必須在系統的設計中體現出來。同一個產品族中的產品能夠是沒有任何關係的對象,可是它們都具備一些共同的約束,如同一操做系統下的按鈕和文本框,按鈕與文本框之間沒有直接關係,但它們都是屬於某一操做系統的,此時具備一個共同的約束條件:操做系統的類型。

       (4) 產品等級結構穩定,設計完成以後,不會向系統中增長新的產品等級結構或者刪除已有的產品等級結構。

疑問

練習

Sunny軟件公司欲推出一款新的手機遊戲軟件,該軟件可以支持Symbian、Android和Windows Mobile等多個智能手機操做系統平臺,針對不一樣的手機操做系統,該遊戲軟件提供了不一樣的遊戲操做控制(OperationController)類和遊戲界面控制(InterfaceController)類,並提供相應的工廠類來封裝這些類的初始化過程。軟件要求具備較好的擴展性以支持新的操做系統平臺,爲了知足上述需求,試採用抽象工廠模式對其進行設計。

相關文章
相關標籤/搜索