第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;
}