舉個生活中常見的例子——組裝電腦,我們在組裝電腦的時候,通常需要選擇一系列的配件,比如CPU、硬盤、內(nèi)存、主板、電源、機箱等。為討論使用簡單點,只考慮選擇CPU和主板的問題。
事實上,在選擇CPU的時候,面臨一系列的問題,比如品牌、型號、針腳數(shù)目、主頻等問題,只有把這些問題都確定下來,才能確定具體的CPU。
同樣,在選擇主板的時候,也有一系列問題,比如品牌、芯片組、集成芯片、總線頻率等問題,也只有這些都確定了,才能確定具體的主板。
選擇不同的CPU和主板,是每個客戶在組裝電腦的時候,向裝機公司提出的要求,也就是我們每個人自己擬定的裝機方案。
在最終確定這個裝機方案之前,還需要整體考慮各個配件之間的兼容性。比如:CPU和主板,如果使用Intel的CPU和AMD的主板是根本無法組裝的。因為Intel的CPU針腳數(shù)與AMD主板提供的CPU插口不兼容,就是說如果使用Intel的CPU根本就插不到AMD的主板中,所以裝機方案是整體性的,里面選擇的各個配件之間是有關(guān)聯(lián)的。
對于裝機工程師而言,他只知道組裝一臺電腦,需要相應(yīng)的配件,但是具體使用什么樣的配件,還得由客戶說了算。也就是說裝機工程師只是負責組裝,而客戶負責選擇裝配所需要的具體的配件。因此,當裝機工程師為不同的客戶組裝電腦時,只需要根據(jù)客戶的裝機方案,去獲取相應(yīng)的配件,然后組裝即可。
使用簡單工廠模式的解決方案
考慮客戶的功能,需要選擇自己需要的CPU和主板,然后告訴裝機工程師自己的選擇,接下來就等著裝機工程師組裝電腦了。
對裝機工程師而言,只是知道CPU和主板的接口,而不知道具體實現(xiàn),很明顯可以用上簡單工廠模式或工廠方法模式。為了簡單,這里選用簡單工廠??蛻舾嬖V裝機工程師自己的選擇,然后裝機工程師會通過相應(yīng)的工廠去獲取相應(yīng)的實例對象。
源代碼
CPU接口與具體實現(xiàn)
public interface Cpu {
public void calculate();
}
public class IntelCpu implements Cpu {
/**
* CPU的針腳數(shù)
*/
private int pins = 0;
public IntelCpu(int pins){
this.pins = pins;
}
@Override
public void calculate() {
// TODO Auto-generated method stub
System.out.println("Intel CPU的針腳數(shù):" + pins);
}
}
public class AmdCpu implements Cpu {
/**
* CPU的針腳數(shù)
*/
private int pins = 0;
public AmdCpu(int pins){
this.pins = pins;
}
@Override
public void calculate() {
// TODO Auto-generated method stub
System.out.println("AMD CPU的針腳數(shù):" + pins);
}
}
主板接口與具體實現(xiàn)
public interface Mainboard {
public void installCPU();
}
public class IntelMainboard implements Mainboard {
/**
* CPU插槽的孔數(shù)
*/
private int cpuHoles = 0;
/**
* 構(gòu)造方法,傳入CPU插槽的孔數(shù)
* @param cpuHoles
*/
public IntelMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU() {
// TODO Auto-generated method stub
System.out.println("Intel主板的CPU插槽孔數(shù)是:" + cpuHoles);
}
}
public class AmdMainboard implements Mainboard {
/**
* CPU插槽的孔數(shù)
*/
private int cpuHoles = 0;
/**
* 構(gòu)造方法,傳入CPU插槽的孔數(shù)
* @param cpuHoles
*/
public AmdMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU() {
// TODO Auto-generated method stub
System.out.println("AMD主板的CPU插槽孔數(shù)是:" + cpuHoles);
}
}
CPU與主板工廠類
public class CpuFactory {
public static Cpu createCpu(int type){
Cpu cpu = null;
if(type == 1){
cpu = new IntelCpu(755);
}else if(type == 2){
cpu = new AmdCpu(938);
}
return cpu;
}
}
public class MainboardFactory {
public static Mainboard createMainboard(int type){
Mainboard mainboard = null;
if(type == 1){
mainboard = new IntelMainboard(755);
}else if(type == 2){
mainboard = new AmdMainboard(938);
}
return mainboard;
}
}
裝機工程師類與客戶類運行結(jié)果如下:
public class ComputerEngineer {
/**
* 定義組裝機需要的CPU
*/
private Cpu cpu = null;
/**
* 定義組裝機需要的主板
*/
private Mainboard mainboard = null;
public void makeComputer(int cpuType , int mainboard){
/**
* 組裝機器的基本步驟
*/
//1:首先準備好裝機所需要的配件
prepareHardwares(cpuType, mainboard);
//2:組裝機器
//3:測試機器
//4:交付客戶
}
private void prepareHardwares(int cpuType , int mainboard){
//這里要去準備CPU和主板的具體實現(xiàn),為了示例簡單,這里只準備這兩個
//可是,裝機工程師并不知道如何去創(chuàng)建,怎么辦呢?
//直接找相應(yīng)的工廠獲取
this.cpu = CpuFactory.createCpu(cpuType);
this.mainboard = MainboardFactory.createMainboard(mainboard);
//測試配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
}
}
public class Client {
public static void main(String[]args){
ComputerEngineer cf = new ComputerEngineer();
cf.makeComputer(1,1);
}
}
運行結(jié)果如下:
上面的實現(xiàn),雖然通過簡單工廠方法解決了:對于裝機工程師,只知CPU和主板的接口,而不知道具體實現(xiàn)的問題。但還有一個問題沒有解決,那就是這些CPU對象和主板對象其實是有關(guān)系的,需要相互匹配的。而上面的實現(xiàn)中,并沒有維護這種關(guān)聯(lián)關(guān)系,CPU和主板是由客戶任意選擇,這是有問題的。比如在客戶端調(diào)用makeComputer時,傳入?yún)?shù)為(1,2),運行結(jié)果如下:
觀察上面結(jié)果就會看出問題??蛻暨x擇的是Intel的CPU針腳數(shù)為755,而選擇的主板是AMD,主板上的CPU插孔是938,根本無法組裝,這就是沒有維護配件之間的關(guān)系造成的。該怎么解決這個問題呢?
引進抽象工廠模式
每一個模式都是針對一定問題的解決方案。抽象工廠模式與工廠方法模式的最大區(qū)別就在于,工廠方法模式針對的是一個產(chǎn)品等級結(jié)構(gòu);而抽象工廠模式則需要面對多個產(chǎn)品等級結(jié)構(gòu)。
在學習抽象工廠具體實例之前,應(yīng)該明白兩個重要的概念:產(chǎn)品族和產(chǎn)品等級。
所謂產(chǎn)品族,是指位于不同產(chǎn)品等級結(jié)構(gòu)中,功能相關(guān)聯(lián)的產(chǎn)品組成的家族。比如AMD的主板、芯片組、CPU組成一個家族,Intel的主板、芯片組、CPU組成一個家族。而這兩個家族都來自于三個產(chǎn)品等級:主板、芯片組、CPU。一個等級結(jié)構(gòu)是由相同的結(jié)構(gòu)的產(chǎn)品組成,示意圖如下:
顯然,每一個產(chǎn)品族中含有產(chǎn)品的數(shù)目,與產(chǎn)品等級結(jié)構(gòu)的數(shù)目是相等的。產(chǎn)品的等級結(jié)構(gòu)與產(chǎn)品族將產(chǎn)品按照不同方向劃分,形成一個二維的坐標系。橫軸表示產(chǎn)品的等級結(jié)構(gòu),縱軸表示產(chǎn)品族,上圖共有兩個產(chǎn)品族,分布于三個不同的產(chǎn)品等級結(jié)構(gòu)中。只要指明一個產(chǎn)品所處的產(chǎn)品族以及它所屬的等級結(jié)構(gòu),就可以唯一的確定這個產(chǎn)品。
上面所給出的三個不同的等級結(jié)構(gòu)具有平行的結(jié)構(gòu)。因此,如果采用工廠方法模式,就勢必要使用三個獨立的工廠等級結(jié)構(gòu)來對付這三個產(chǎn)品等級結(jié)構(gòu)。由于這三個產(chǎn)品等級結(jié)構(gòu)的相似性,會導致三個平行的工廠等級結(jié)構(gòu)。隨著產(chǎn)品等級結(jié)構(gòu)的數(shù)目的增加,工廠方法模式所給出的工廠等級結(jié)構(gòu)的數(shù)目也會隨之增加。如下圖:
那么,是否可以使用同一個工廠等級結(jié)構(gòu)來對付這些相同或者極為相似的產(chǎn)品等級結(jié)構(gòu)呢?當然可以的,而且這就是抽象工廠模式的好處。同一個工廠等級結(jié)構(gòu)負責三個不同產(chǎn)品等級結(jié)構(gòu)中的產(chǎn)品對象的創(chuàng)建。
可以看出,一個工廠等級結(jié)構(gòu)可以創(chuàng)建出分屬于不同產(chǎn)品等級結(jié)構(gòu)的一個產(chǎn)品族中的所有對象。顯然,這時候抽象工廠模式比簡單工廠模式、工廠方法模式更有效率。對應(yīng)于每一個產(chǎn)品族都有一個具體工廠。而每一個具體工廠負責創(chuàng)建屬于同一個產(chǎn)品族,但是分屬于不同等級結(jié)構(gòu)的產(chǎn)品。
抽象工廠模式結(jié)構(gòu)
抽象工廠模式是對象的創(chuàng)建模式,它是工廠方法模式的進一步推廣。
假設(shè)一個子系統(tǒng)需要一些產(chǎn)品對象,而這些產(chǎn)品又屬于一個以上的產(chǎn)品等級結(jié)構(gòu)。那么為了將消費這些產(chǎn)品對象的責任和創(chuàng)建這些產(chǎn)品對象的責任分割開來,可以引進抽象工廠模式。這樣的話,消費產(chǎn)品的一方不需要直接參與產(chǎn)品的創(chuàng)建工作,而只需要向一個公用的工廠接口請求所需要的產(chǎn)品。
通過使用抽象工廠模式,可以處理具有相同(或者相似)等級結(jié)構(gòu)中的多個產(chǎn)品族中的產(chǎn)品對象的創(chuàng)建問題。如下圖所示:
由于這兩個產(chǎn)品族的等級結(jié)構(gòu)相同,因此使用同一個工廠族也可以處理這兩個產(chǎn)品族的創(chuàng)建問題,這就是抽象工廠模式。
根據(jù)產(chǎn)品角色的結(jié)構(gòu)圖,就不難給出工廠角色的結(jié)構(gòu)設(shè)計圖。
可以看出,每一個工廠角色都有兩個工廠方法,分別負責創(chuàng)建分屬不同產(chǎn)品等級結(jié)構(gòu)的產(chǎn)品對象。
源代碼
前面示例實現(xiàn)的CPU接口和CPU實現(xiàn)對象,主板接口和主板實現(xiàn)對象,都不需要變化。
前面示例中創(chuàng)建CPU的簡單工廠和創(chuàng)建主板的簡單工廠,都不再需要。
新加入的抽象工廠類和實現(xiàn)類:
public interface AbstractFactory {
/**
* 創(chuàng)建CPU對象
* @return CPU對象
*/
public Cpu createCpu();
/**
* 創(chuàng)建主板對象
* @return 主板對象
*/
public Mainboard createMainboard();
}
public class IntelFactory implements AbstractFactory {
@Override
public Cpu createCpu() {
// TODO Auto-generated method stub
return new IntelCpu(755);
}
@Override
public Mainboard createMainboard() {
// TODO Auto-generated method stub
return new IntelMainboard(755);
}
}
public class AmdFactory implements AbstractFactory {
@Override
public Cpu createCpu() {
// TODO Auto-generated method stub
return new IntelCpu(938);
}
@Override
public Mainboard createMainboard() {
// TODO Auto-generated method stub
return new IntelMainboard(938);
}
}
裝機工程師類跟前面的實現(xiàn)相比,主要的變化是:從客戶端不再傳入選擇CPU和主板的參數(shù),而是直接傳入客戶已經(jīng)選擇好的產(chǎn)品對象。這樣就避免了單獨去選擇CPU和主板所帶來的兼容性問題,客戶要選就是一套,就是一個系列。
public class ComputerEngineer {
/**
* 定義組裝機需要的CPU
*/
private Cpu cpu = null;
/**
* 定義組裝機需要的主板
*/
private Mainboard mainboard = null;
public void makeComputer(AbstractFactory af){
/**
* 組裝機器的基本步驟
*/
//1:首先準備好裝機所需要的配件
prepareHardwares(af);
//2:組裝機器
//3:測試機器
//4:交付客戶
}
private void prepareHardwares(AbstractFactory af){
//這里要去準備CPU和主板的具體實現(xiàn),為了示例簡單,這里只準備這兩個
//可是,裝機工程師并不知道如何去創(chuàng)建,怎么辦呢?
//直接找相應(yīng)的工廠獲取
this.cpu = af.createCpu();
this.mainboard = af.createMainboard();
//測試配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
}
}
客戶端代碼:
public class Client {
public static void main(String[]args){
//創(chuàng)建裝機工程師對象
ComputerEngineer cf = new ComputerEngineer();
//客戶選擇并創(chuàng)建需要使用的產(chǎn)品對象
AbstractFactory af = new IntelFactory();
//告訴裝機工程師自己選擇的產(chǎn)品,讓裝機工程師組裝電腦
cf.makeComputer(af);
}
}
抽象工廠的功能是為一系列相關(guān)對象或相互依賴的對象創(chuàng)建一個接口。一定要注意,這個接口內(nèi)的方法不是任意堆砌的,而是一系列相關(guān)或相互依賴的方法。比如上面例子中的主板和CPU,都是為了組裝一臺電腦的相關(guān)對象。不同的裝機方案,代表一種具體的電腦系列。
由于抽象工廠定義的一系列對象通常是相關(guān)或相互依賴的,這些產(chǎn)品對象就構(gòu)成了一個產(chǎn)品族,也就是抽象工廠定義了一個產(chǎn)品族。
這就帶來非常大的靈活性,切換產(chǎn)品族的時候,只要提供不同的抽象工廠實現(xiàn)就可以了,也就是說現(xiàn)在是以一個產(chǎn)品族作為一個整體被切換。
在什么情況下應(yīng)當使用抽象工廠模式
1.一個系統(tǒng)不應(yīng)當依賴于產(chǎn)品類實例如何被創(chuàng)建、組合和表達的細節(jié),這對于所有形態(tài)的工廠模式都是重要的。
2.這個系統(tǒng)的產(chǎn)品有多于一個的產(chǎn)品族,而系統(tǒng)只消費其中某一族的產(chǎn)品。
3.同屬于同一個產(chǎn)品族的產(chǎn)品是在一起使用的,這一約束必須在系統(tǒng)的設(shè)計中體現(xiàn)出來。(比如:Intel主板必須使用Intel CPU、Intel芯片組)
4.系統(tǒng)提供一個產(chǎn)品類的庫,所有的產(chǎn)品以同樣的接口出現(xiàn),從而使客戶端不依賴于實現(xiàn)。
抽象工廠模式的起源
抽象工廠模式的起源或者最早的應(yīng)用,是用于創(chuàng)建分屬于不同操作系統(tǒng)的視窗構(gòu)建。比如:命令按鍵(Button)與文字框(Text)都是視窗構(gòu)建,在UNIX操作系統(tǒng)的視窗環(huán)境和Windows操作系統(tǒng)的視窗環(huán)境中,這兩個構(gòu)建有不同的本地實現(xiàn),它們的細節(jié)有所不同。
在每一個操作系統(tǒng)中,都有一個視窗構(gòu)建組成的構(gòu)建家族。在這里就是Button和Text組成的產(chǎn)品族。而每一個視窗構(gòu)件都構(gòu)成自己的等級結(jié)構(gòu),由一個抽象角色給出抽象的功能描述,而由具體子類給出不同操作系統(tǒng)下的具體實現(xiàn)。
可以發(fā)現(xiàn)在上面的產(chǎn)品類圖中,有兩個產(chǎn)品的等級結(jié)構(gòu),分別是Button等級結(jié)構(gòu)和Text等級結(jié)構(gòu)。同時有兩個產(chǎn)品族,也就是UNIX產(chǎn)品族和Windows產(chǎn)品族。UNIX產(chǎn)品族由UNIX Button和UNIX Text產(chǎn)品構(gòu)成;而Windows產(chǎn)品族由Windows Button和Windows Text產(chǎn)品構(gòu)成。
系統(tǒng)對產(chǎn)品對象的創(chuàng)建需求由一個工程的等級結(jié)構(gòu)滿足,其中有兩個具體工程角色,即UnixFactory和WindowsFactory。UnixFactory對象負責創(chuàng)建Unix產(chǎn)品族中的產(chǎn)品,而WindowsFactory對象負責創(chuàng)建Windows產(chǎn)品族中的產(chǎn)品。這就是抽象工廠模式的應(yīng)用,抽象工廠模式的解決方案如下圖:
顯然,一個系統(tǒng)只能夠在某一個操作系統(tǒng)的視窗環(huán)境下運行,而不能同時在不同的操作系統(tǒng)上運行。所以,系統(tǒng)實際上只能消費屬于同一個產(chǎn)品族的產(chǎn)品。
在現(xiàn)代的應(yīng)用中,抽象工廠模式的使用范圍已經(jīng)大大擴大了,不再要求系統(tǒng)只能消費某一個產(chǎn)品族了。因此,可以不必理會前面所提到的原始用意。
抽象工廠模式的優(yōu)點
分離接口和實現(xiàn)
客戶端使用抽象工廠來創(chuàng)建需要的對象,而客戶端根本就不知道具體的實現(xiàn)是誰,客戶端只是面向產(chǎn)品的接口編程而已。也就是說,客戶端從具體的產(chǎn)品實現(xiàn)中解耦。
使切換產(chǎn)品族變得容易
因為一個具體的工廠實現(xiàn)代表的是一個產(chǎn)品族,比如上面例子的從Intel系列到AMD系列只需要切換一下具體工廠。
抽象工廠模式的缺點
不太容易擴展新的產(chǎn)品
如果需要給整個產(chǎn)品族添加一個新的產(chǎn)品,那么就需要修改抽象工廠,這樣就會導致修改所有的工廠實現(xiàn)類。