1. 程式人生 > >第5章 建立型模式—抽象工廠模式

第5章 建立型模式—抽象工廠模式

1. 抽象工廠的定義

(1)提供一個建立一系列相關相互依賴物件的介面,而無需指定它們具體的類

  ①只需要知道建立一系列物件的介面,而無需知道具體使用的是哪一個實現

  ②這一系列物件是相關或相互依賴的,也就是說既要建立物件,還要約束它們之間的關係。

  ③一系列物件是構建新物件所需要的組成部分,並且物件之間相互有約束。如電腦由CPU和主機板等組成,但CPU的針腳數和主機板提供的插口必須是匹配的,否則無法組裝。

(2)產品族和產品等級

  ①產品族:在抽象工廠模式中,產品族是指由同一個工廠生產的,位於不同產品等級結構,功能相關聯的產品組成的家族。如,AMD的主機板、晶片組、CPU組成一個家族,Intel的主機板、晶片組、CPU組成一個家族。

  ②產品等級結構:產品等級結構即產品的繼承結構。如抽象電視機與具體品牌的電視機之間構成了一個產品等級結構。如,AMD和Intel這兩個家族都來自於三個產品等級:主機板、晶片組、CPU。一個等級結構是由相同的結構的產品組成。

(2)抽象工廠模式的結構圖

  ①Abstract Factory:抽象工廠,定義建立一系列產品物件的操作介面

  ②Concrete Factory:具體的工廠,實現抽象工廠定義的方法,具體實現一系列物件的建立。

  ③Abstract Product:定義一類產品物件的介面

  ④Concrete Product:具體的產品實現物件,通常在具體工廠裡面,會選擇具體的產品實現物件,來建立符合抽象工廠類中定義的相應的介面型別物件。

  ⑤Client:客戶端,主要使用抽象工廠來獲取一系列所需要的產品物件,然後面向這些產品物件的介面程式設計。

2. 思考抽象工廠模式

(1)抽象工廠模式的本質:選擇產品簇的實現,即每個具體工廠建立的是一系列的產品。

  ①工廠方法針對的是單個產品物件的建立,本質是選擇單個產品的實現。而抽象工廠著重的是建立一個產品簇的實現,在抽象工廠類裡定義的介面通常是有聯絡的,它們都是產品的某一部分或者是相互依賴的。注意,這些介面不是任意堆砌的,而是一系列相關或相互依賴的方法。

  ②如果抽象工廠裡面只定義一個方法,直接建立產品,那就退化為工廠方法了。

(2)切換產品簇:只要提供不同的抽象工廠實現,產品簇就可以作為一個整體被切換

【程式設計實驗】電腦裝機方案

//建立型模式:抽象工廠模式
//電腦組裝
#include <stdio.h>

////////////////////////////////////////////產品(等級)////////////////////////////
//產品等級1:
//1、抽象CPU類
class CPUApi
{
public:
    virtual void calculate() = 0;
};

//1.1 AMD CPU(具體CPU產品類)
class AMDCPU : public CPUApi
{
private:
    int pints;
public:
    AMDCPU(int pints){this->pints = pints;}
    void calculate()
    {
        printf("now in AMD CPU, pints = %d\n",pints);
    }    
};

//1.2 Intel CPU(具體CPU產品類)
class IntelCPU : public CPUApi
{
private:
    int pints;
public:
    IntelCPU(int pints){this->pints = pints;}
    void calculate()
    {
        printf("now in Intel CPU, pints = %d\n",pints);
    }    
};

//產品等級2
//2.抽象主機板類
class MainboardApi
{
public:
    virtual void installCPU() = 0;  
};

//2.1 技嘉主機板
class GAMainboard : public MainboardApi
{
private:
    int cpuHoles; //CPU插槽的孔數
public:
    GAMainboard(int cpuHoles){this->cpuHoles = cpuHoles;}
    void installCPU()
    {
        printf("now in GAMainboard, cpuHoles = %d\n", cpuHoles);    
    }    
};

//2.2 微星主機板
class MSIMainboard : public MainboardApi
{
private:
    int cpuHoles; //CPU插槽的孔數
public:
    MSIMainboard(int cpuHoles){this->cpuHoles = cpuHoles;}
    void installCPU()
    {
        printf("now in MSIMainboard, cpuHoles = %d\n", cpuHoles);    
    }    
};

////////////////////////////////////////////工廠類////////////////////////////
//3.抽象工廠介面,宣告建立抽象產品物件的操作
class AbstractFactory
{
public:
    //建立CPU物件
    virtual CPUApi* createCPUApi() = 0;
    
    //建立主機板物件
    virtual MainboardApi* createMainboardApi() = 0;    
};

//3.1具體工廠(裝機方案1):Intel的CPU+技嘉主機板
//  這裡建立的CPU和主機板物件是能匹配的,即有約束關係  
class Schema1 : public AbstractFactory
{
public:
    //CPU
    CPUApi* createCPUApi()
    {
        return new IntelCPU(1156);
    }
    
    //主機板物件
    MainboardApi* createMainboardApi()
    {
        return new GAMainboard(1156);
    }    
};

//3.2具體工廠(裝機方案2):AMD的CPU+微星主機板
//  這裡建立的CPU和主機板物件是能匹配的,即有約束關係  
class Schema2 : public AbstractFactory
{
public:
    //CPU
    CPUApi* createCPUApi()
    {
        return new AMDCPU(939);
    }
    
    //主機板物件
    MainboardApi* createMainboardApi()
    {
        return new MSIMainboard(939);
    }    
};

int main()
{
    //客戶端呼叫例子
    
    //工廠
    AbstractFactory* af = new Schema1(); //可以整體換裝機方案
    
    //CPU
    CPUApi* cpu = af->createCPUApi();
    
    //主機板
    MainboardApi* mainboard = af->createMainboardApi();
    
    //測試一下配件是否正常
    cpu->calculate();
    mainboard->installCPU();
    
    delete cpu;
    delete mainboard;
    delete af;
    
    return 0;
}

3. 抽象工廠模式的優缺點

(1)優點

  ①封裝性:抽象工廠模式隔離了具體類的生成,使得客戶並不需要知道什麼被建立。由於這種隔離,更換一個具體工廠就變得相對容易。所有的具體工廠都實現了抽象工廠中定義的那些公共介面,因此只需改變具體工廠的例項,就可以在某種程度上改變整個軟體系統的行為。另外,應用抽象工廠模式可以實現高內聚低耦合的設計目的,因此抽象工廠模式得到了廣泛的應用。

  ②當一個產品族中的多個物件被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的物件(即產品物件是有約束的,不可以隨心所欲的建立物件)。這對一些需要根據當前環境來決定其行為的軟體系統來說,是一種非常實用的設計模式。

  ③增加新的具體工廠和產品族很方便,無須修改已有系統,符合“開閉原則”

(2)缺點

  ①在新增新的產品物件時,難以擴充套件抽象工廠來生產新種類的產品,這是因為在抽象工廠角色中規定了所有可能被建立的產品集合,要支援新種類的產品就意味著要對該介面進行擴充套件,而這將涉及到對抽象工廠角色及其所有子類的修改,顯然會帶來較大的不便。

  ②開閉原則的傾斜性(增加新的工廠和產品族容易,增加新的產品等級結構麻煩)。

4. 抽象工廠模式的使用場景

(1)一個系統不應當依賴於產品類例項如何被建立、組合和表達的細節,這對於所有形態的工廠模式都是重要的。

(2)這個系統的產品有多於一個的產品族,而系統只消費其中某一族的產品

(3)同屬於同一個產品族的產品是在一起使用的,這一約束必須在系統的設計中體現出來。說的更明白一點,就是一個繼承體系中,如果存在著多個等級結構(即存在著多個抽象類),並且分屬各個等級結構中的實現類之間存在著一定的關聯或者約束(比如:Intel主機板必須使用Intel CPU、Intel晶片組),就可以使用抽象工廠模式。假如各個等級結構中的實現類之間不存在關聯或約束,則使用多個獨立的工廠來對產品進行建立,則更合適一點

5. 抽象工廠模式和DAO

(1)資料訪問物件(Data Access Object,DAO):用於解決訪問資料物件所面臨的一系列問題。

  ①資料來源不同,如本地資料來源和遠端伺服器上的資料來源

  ②儲存型別不同(如關係型資料庫(RDBMS)、XML、純檔案等。

  ③訪問方式不同,比如ODBC、ADO

  ④供應商不同,如Oracle、DB2、SqlServer、MySQL等等。

  ⑤版本不同,如關係型資料庫,不同版本,實現功能是有差異的。就算是對標準的SQL的支援,也是有差異的。

(2)DAO和抽象工廠的關係

  ①DAO模式最常見的實現策略是工廠的策略,而且多是通過抽象工廠模式來實現。

  ②在使用抽象工廠模式時,也可以結合工廠方法模式。

(3)DAO訪問方式的工廠實現策略(以訂單處理為例)

  ①訂單通常分為主記錄(主表)和明細記錄(子表)

  ②當業務物件需要操作訂單的主表時,一般也需要操作子表

  ③如果業務簡單,且對資料的操作是固定的,即不管訂單業務如何變化,底層資料儲存都是一樣的,那麼這時可以採用工廠方法模式。如果底層儲存不固定(如存在資料庫,或Xml檔案),則一般採用抽象工廠模式

【程式設計例項】利用抽象工廠模式將業務物件儲存在不同的資料倉庫

//建立型模式:抽象工廠模式
//資料訪問物件策略的抽象工廠實現
#include <stdio.h>

//以下的所說的資料倉庫可以是關係型係數據庫或Xml

//產品物件的介面,就是訂單主、子記錄的DAO定義
//訂單主記錄的DAO定義:(提供儲存業務物件到資料倉庫的功能)

class COrderMainDAO
{
public:
    virtual void saveOrderMain() = 0;
};

//訂單子記錄的DAO定義(提供儲存業務物件到資料倉庫的功能)

class COrderDetailDAO
{
public:
    virtual void saveOrderDetail() = 0;   
};

//Rdb實現:將業務物件儲存到關係型資料庫
class CRdbMainDAOImpl : public COrderMainDAO
{
public:
    void  saveOrderMain()
    {
        printf("now in RdbMainDAOImpl saveOrderMain\n");
    }   
};

//Rdb實現:將業務物件儲存到關係型資料庫
class CRdbDetailDAOImpl : public COrderDetailDAO
{
public:
    void  saveOrderDetail()
    {
        printf("now in RdbDetailDAOImpl saveOrderDetail\n");
    }   
};

//Xml實現:將業務物件儲存到xml檔案
class CXmlMainDAOImpl : public COrderMainDAO
{
public:
    void saveOrderMain()
    {
        printf("now in XmlMainDAOImpl saveOrderMain\n");        
    }
};

//Xml實現:將業務物件儲存到xml檔案
class CXmlDetailDAOImpl : public COrderDetailDAO
{
public:
    void  saveOrderDetail()
    {
        printf("now in XmlDetailDAOImpl saveOrderDetail\n");
    }   
};


//抽象工廠,建立訂單主、子記錄對應的DAO物件
class CDAOFactory
{
public:
    //訂單主記錄對應的DAO物件
    virtual COrderMainDAO* createOrderMainDAO() = 0; 
    
    //訂單子記錄對應的DAO物件
    virtual COrderDetailDAO* createOrderDetailDAO() = 0;
};

//具體工廠
//關係型資料庫的實現方式
class CRdbDAOFactory : public CDAOFactory
{
public:
    COrderMainDAO* createOrderMainDAO()
    {
        return new CRdbMainDAOImpl();
    }   
    
    COrderDetailDAO* createOrderDetailDAO()
    {
        return new CRdbDetailDAOImpl();
    }    
};

//Xml的實現方式
class CXmlDAOFactory : public CDAOFactory
{
public:
    COrderMainDAO* createOrderMainDAO()
    {
        return new CXmlMainDAOImpl();
    }   
    
    COrderDetailDAO* createOrderDetailDAO()
    {
        return new CXmlDetailDAOImpl();
    }    
};


int main()
{
    //客戶端呼叫例子
    
    //建立DAO的抽象工廠
    //CDAOFactory* df = new CRdbDAOFactory();
    CDAOFactory* df = new CXmlDAOFactory();
    
    //通過抽象工廠來獲取需要的DAO介面
    COrderMainDAO* mainDAO = df->createOrderMainDAO();
    COrderDetailDAO* detailDAO = df->createOrderDetailDAO();
    
    //呼叫DAO來完成資料儲存的功能
    mainDAO->saveOrderMain();
    detailDAO->saveOrderDetail();
    
    delete mainDAO;
    delete detailDAO;
    delete df;
 
    return 0;
}