【白話設計模式六】抽象工廠模式(Abstract Factory)

#0 系列目錄#數據庫

#1 場景問題# ##1.1 選擇組裝電腦的配件## 舉個生活中常見的例子——組裝電腦,咱們在組裝電腦的時候,一般須要選擇一系列的配件,好比:CPU、硬盤、內存、主板、電源、機箱等等。爲了使討論簡單點,只考慮選擇CPU和主板的問題。編程

事實上,咱們在選擇CPU的時候,面臨一系列的問題,好比:品牌、型號、針腳數目、主頻等問題,只有把這些都肯定下來,才能肯定具體的CPU。一樣,在選擇主板的時候,也有一系列的問題,好比:品牌、芯片組、集成芯片、總線頻率等問題,也只有這些都肯定了,才能肯定具體的主板。設計模式

選擇不一樣的CPU和主板,是每一個客戶去組裝電腦的時候,向裝機公司提出的要求,也就是咱們每一個人本身擬定的裝機方案。緩存

在最終肯定這個裝機方案以前,還須要總體考慮各個配件之間的兼容性,好比:CPU和主板,若是CPU針腳數和主板提供的CPU插口不兼容,是沒法組裝的。也就是說,裝機方案是有總體性的,裏面選擇的各個配件之間是有關聯的安全

對於裝機工程師而言,他只知道組裝一臺電腦,須要相應的配件,可是具體使用什麼樣的配件,還得由客戶說了算。也就是說裝機工程師只是負責組裝,而客戶負責選擇裝配所須要的具體的配件。所以,當裝機工程師爲不一樣的客戶組裝電腦時,只須要按照客戶的裝機方案,去獲取相應的配件,而後組裝便可。服務器

如今須要使用程序來把這個裝機的過程,尤爲是選擇組裝電腦配件的過程實現出來,該如何實現呢?框架

##1.2 不用模式的解決方案## 考慮客戶的功能,須要選擇本身須要的CPU和主板,而後告訴裝機工程師本身的選擇,接下來就等着裝機工程師組裝機器了。測試

對裝機工程師而言,只是知道CPU和主板的接口,而不知道具體實現,很明顯能夠用上簡單工廠或工廠方法模式,爲了簡單,這裏選用簡單工廠吧。客戶告訴裝機工程師本身的選擇,而後裝機工程師會經過相應的工廠去獲取相應的實例對象。ui

  1. 先來看看CPU和主板的接口,先看CPU的接口定義,示例代碼以下:
/** 
 * CPU的接口 
 */  
public interface CPUApi {  
    /** 
     * 示意方法,CPU具備運算的功能 
     */  
    public void calculate();  
}

再看看主板的接口定義,示例代碼以下:this

/**
 * Mainboard的接口
 */
public interface MainboardApi {
    public void installCPU();
}
  1. 接下來看看具體的CPU實現,先看Intel的CPU實現,示例代碼以下:
/** 
 * Intel的CPU實現 
 */  
public class IntelCPU implements CPUApi{  
    /** 
     * CPU的針腳數目 
     */  
    private int pins = 0;  
    /** 
     * 構造方法,傳入CPU的針腳數目 
     * @param pins CPU的針腳數目 
     */  
    public IntelCPU(int pins){  
        this.pins = pins;  
    }  
    public void calculate() {  
        System.out.println("now in Intel CPU,pins="+pins);  
    }  
}

再看看AMD的CPU實現,示例代碼以下:

/** 
 * AMD的CPU實現 
 */  
public class AMDCPU implements CPUApi{  
    /** 
     * CPU的針腳數目 
     */  
    private int pins = 0;  
    /** 
     * 構造方法,傳入CPU的針腳數目 
     * @param pins CPU的針腳數目 
     */  
    public AMDCPU(int pins){  
        this.pins = pins;  
    }  
    public void calculate() {  
        System.out.println("now in AMD CPU,pins="+pins);  
    }  
}
  1. 接下來看看具體的主板實現,先看技嘉的主板實現,示例代碼以下:
/** 
 * 技嘉的主板 
 */  
public class GAMainboard implements MainboardApi {  
    /** 
     * CPU插槽的孔數 
     */  
    private int cpuHoles = 0;  
    /** 
     * 構造方法,傳入CPU插槽的孔數 
     * @param cpuHoles CPU插槽的孔數 
     */  
    public GAMainboard(int cpuHoles){  
        this.cpuHoles = cpuHoles;  
    }  
    public void installCPU() {  
        System.out.println("now in GAMainboard,cpuHoles=" + cpuHoles);  
    }  
}

再看看微星的主板實現,示例代碼以下:

/** 
 * 微星的主板 
 */  
public class MSIMainboard implements MainboardApi{  
    /** 
     * CPU插槽的孔數 
     */  
    private int cpuHoles = 0;  
    /** 
     * 構造方法,傳入CPU插槽的孔數 
     * @param cpuHoles CPU插槽的孔數 
     */  
    public MSIMainboard(int cpuHoles){  
        this.cpuHoles = cpuHoles;  
    }  
    public void installCPU() {  
        System.out.println("now in MSIMainboard,cpuHoles=" + cpuHoles);  
    }
}
  1. 接下來看看建立CPU和主板的工廠,先看建立CPU的工廠實現,示例代碼以下:
/** 
 * 建立CPU的簡單工廠 
 */  
public class CPUFactory {  
    /** 
     * 建立CPU接口對象的方法 
     * @param type 選擇CPU類型的參數 
     * @return CPU接口對象的方法 
     */  
    public static CPUApi createCPUApi(int type){  
        CPUApi cpu = null;  
        //根據參數來選擇並建立相應的CPU對象  
        if(type==1){  
            cpu = new IntelCPU(1156);  
        }else if(type==2){  
            cpu = new AMDCPU(939);  
        }  
        return cpu;  
    }    
}

再看看建立主板的工廠實現,示例代碼以下:

/** 
 * 建立主板的簡單工廠 
 */  
public class MainboardFactory {  
    /** 
     * 建立主板接口對象的方法 
     * @param type 選擇主板類型的參數 
     * @return 主板接口對象的方法 
     */  
    public static MainboardApi createMainboardApi(int type){  
        MainboardApi mainboard = null;  
        //根據參數來選擇並建立相應的主板對象  
        if(type==1){  
            mainboard = new GAMainboard(1156);  
        }else if(type==2){  
            mainboard = new MSIMainboard(939);  
        }  
        return mainboard;  
    }  
}
  1. 接下來看看裝機工程師的實現,示例代碼以下:
/** 
 * 裝機工程師的類 
 */  
public class ComputerEngineer {  
    /** 
     * 定義組裝機器須要的CPU 
     */  
    private CPUApi cpu= null;  
    /** 
     * 定義組裝機器須要的主板 
     */  
    private MainboardApi mainboard = null;  
    /** 
     * 裝機過程 
     * @param cpuType 客戶選擇所需CPU的類型 
     * @param mainboardType 客戶選擇所需主板的類型 
     */  
    public void makeComputer(int cpuType,int mainboardType){  
        //1:首先準備好裝機所須要的配件  
        prepareHardwares(cpuType,mainboardType);  
        //2:組裝機器       
        //3:測試機器       
        //4:交付客戶  
    }  
    /** 
     * 準備裝機所須要的配件 
     * @param cpuType 客戶選擇所需CPU的類型 
     * @param mainboardType 客戶選擇所需主板的類型 
     */  
    private void prepareHardwares(int cpuType,int mainboardType){  
        //這裏要去準備CPU和主板的具體實現,爲了示例簡單,這裏只准備這兩個  
        //但是,裝機工程師並不知道如何去建立,怎麼辦呢?  

        //直接找相應的工廠獲取  
        this.cpu = CPUFactory.createCPUApi(cpuType);  
        this.mainboard = MainboardFactory.createMainboardApi(mainboardType);  
        //測試一下配件是否好用  
        this.cpu.calculate();  
        this.mainboard.installCPU();  
    }  
}
  1. 看看此時的客戶端,應該經過裝機工程師來組裝電腦,客戶須要告訴裝機工程師他選擇的配件,示例代碼以下:
public class Client {  
    public static void main(String[] args) {  
        //建立裝機工程師對象  
        ComputerEngineer engineer = new ComputerEngineer();  
        //告訴裝機工程師本身選擇的配件,讓裝機工程師組裝電腦  
        engineer.makeComputer(1,1);  
    }  
}

##1.3 有何問題## 看了上面的實現,會感受到很簡單嘛,經過使用簡單工廠來獲取須要的CPU和主板對象,而後就能夠組裝電腦了。有何問題呢?

雖然上面的實現,經過簡單工廠解決解決了:對於裝機工程師,只知CPU和主板的接口,而不知道具體實現的問題。但還有一個問題沒有解決,什麼問題呢?那就是這些CPU對象和主板對象實際上是有關係的,是須要相互匹配的。而在上面的實現中,並無維護這種關聯關係,CPU和主板是由客戶隨意選擇的。這是有問題的。

這就是沒有維護配件之間的關係形成的。該怎麼解決這個問題呢?

#2 解決方案# ##2.1 抽象工廠模式來解決## 用來解決上述問題的一個合理的解決方案就是抽象工廠模式。那麼什麼是抽象工廠模式呢?

  1. 抽象工廠模式定義

輸入圖片說明

  1. 應用抽象工廠模式來解決的思路

仔細分析上面的問題,其實有兩個問題點,一個是隻知道所須要的一系列對象的接口,而不知具體實現,或者是不知道具體使用哪個實現;另一個是這一系列對象是相關或者相互依賴的。也就是說既要建立接口的對象,還要約束它們之間的關係。

有朋友可能會想,工廠方法模式或者是簡單工廠,不就能夠解決只知接口而不知實現的問題嗎?怎麼這些問題又冒出來了呢?

請注意,這裏要解決的問題和工廠方法模式或簡單工廠解決的問題是有很大不一樣的,工廠方法模式或簡單工廠關注的是單個產品對象的建立,好比建立CPU的工廠方法,它就只關心如何建立CPU的對象,而建立主板的工廠方法,就只關心如何建立主板對象。

這裏要解決的問題是,要建立一系列的產品對象,並且這一系列對象是構建新的對象所須要的組成部分,也就是這一系列被建立的對象相互之間是有約束的。

**解決這個問題的一個解決方案就是抽象工廠模式。**在這個模式裏面,會定義一個抽象工廠,在裏面虛擬的建立客戶端須要的這一系列對象,所謂虛擬的就是定義建立這些對象的抽象方法,並不去真的實現,而後由具體的抽象工廠的子類來提供這一系列對象的建立。這樣一來能夠爲同一個抽象工廠提供不少不一樣的實現,那麼建立的這一系列對象也就不同了,也就是說,抽象工廠在這裏起到一個約束的做用,並提供全部子類的一個統一外觀,來讓客戶端使用。

##2.2 模式結構和說明## 抽象工廠模式結構如圖所示:

輸入圖片說明

AbstractFactory:抽象工廠,定義建立一系列產品對象的操做接口。

ConcreteFactory:具體的工廠,實現抽象工廠定義的方法,具體實現一系列產品對象的建立。

AbstractProduct:定義一類產品對象的接口。

ConcreteProduct:具體的產品實現對象,一般在具體工廠裏面,會選擇具體的產品實現對象,來建立符合抽象工廠定義的方法返回的產品類型的對象。

Client:客戶端,主要使用抽象工廠來獲取一系列所須要的產品對象,而後面向這些產品對象的接口編程,以實現須要的功能。

##2.3 抽象工廠模式示例代碼##

  1. 先看看抽象工廠的定義,示例代碼以下:
/** 
 * 抽象工廠的接口,聲明建立抽象產品對象的操做 
 */  
public interface AbstractFactory {  
    /** 
     * 示例方法,建立抽象產品A的對象 
     * @return 抽象產品A的對象 
     */  
    public AbstractProductA createProductA();  
    /** 
     * 示例方法,建立抽象產品B的對象 
     * @return 抽象產品B的對象 
     */  
    public AbstractProductB createProductB();  
}
  1. 接下來看看產品的定義,因爲只是示意,並無去定義具體的方法,示例代碼以下:
/** 
 * 抽象產品A的接口 
 */  
public interface AbstractProductA {  
    //定義抽象產品A相關的操做  
} 

/** 
 * 抽象產品B的接口 
 */  
public interface AbstractProductB {  
    //定義抽象產品B相關的操做  
}
  1. 一樣的,產品的各個實現對象也是空的,示例代碼以下:
/** 
 * 產品A的具體實現 
 */  
public class ProductA1 implements AbstractProductA {  
    //實現產品A的接口中定義的操做  
}  

/** 
 * 產品A的具體實現 
 */  
public class ProductA2 implements AbstractProductA {  
    //實現產品A的接口中定義的操做  
} 

/** 
 * 產品B的具體實現 
 */  
public class ProductB1 implements AbstractProductB {  
    //實現產品B的接口中定義的操做  
}  

/** 
 * 產品B的具體實現 
 */  
public class ProductB2 implements AbstractProductB {  
    //實現產品B的接口中定義的操做  
}
  1. 接下來看看具體的工廠的實現示意,示例代碼以下:
/** 
 * 具體的工廠實現對象,實現建立具體的產品對象的操做 
 */  
public class ConcreteFactory1 implements AbstractFactory {  
    public AbstractProductA createProductA() {  
        return new ProductA1();  
    }  
    public AbstractProductB createProductB() {  
        return new ProductB1();  
    }  
} 

/** 
 * 具體的工廠實現對象,實現建立具體的產品對象的操做 
 */  
public class ConcreteFactory2 implements AbstractFactory {  
    public AbstractProductA createProductA() {  
        return new ProductA2();  
    }  
    public AbstractProductB createProductB() {  
        return new ProductB2();  
    }  
}
  1. 最後來看看客戶端的實現示意,示例代碼以下:
public class Client {  
    public static void main(String[] args) {  
        //建立抽象工廠對象  
        AbstractFactory af = new ConcreteFactory1();  
        //經過抽象工廠來獲取一系列的對象,如產品A和產品B  
        af.createProductA();  
        af.createProductB();  
    }  
}

##2.4 使用抽象工廠模式重寫示例## 要使用抽象工廠模式來重寫示例,先來看看如何使用抽象工廠模式來解決前面提出的問題。

裝機工程師要組裝電腦對象,須要一系列的產品對象,好比CPU、主板等,因而建立一個抽象工廠給裝機工程師使用,在這個抽象工廠裏面定義抽象的建立CPU和主板的方法,這個抽象工廠就至關於一個抽象的裝機方案,在這個裝機方案裏面,各個配件是可以相互匹配的。

每一個裝機的客戶,會提出他們本身的具體裝機方案,或者是選擇已有的裝機方案,至關於爲抽象工廠提供了具體的子類,在這些具體的裝機方案類裏面,會建立具體的CPU和主板實現對象。

此時系統的結構如圖所示:

輸入圖片說明

雖說是重寫示例,但並非前面寫的都不要了,而是修改前面的示例,使它能更好的實現須要的功能。

  1. 前面示例實現的CPU接口和CPU實現對象,還有主板的接口和實現對象,都不須要變化,這裏就不去贅述了。

  2. 前面示例中的建立CPU的簡單工廠和建立主板的簡單工廠,都再也不須要了,直接刪除便可,這裏也就不去管了。

  3. 看看新加入的抽象工廠的定義,示例代碼以下:

/** 
 * 抽象工廠的接口,聲明建立抽象產品對象的操做 
 */  
public interface AbstractFactory {  
    /** 
     * 建立CPU的對象 
     * @return CPU的對象 
     */  
    public CPUApi createCPUApi();  
    /** 
     * 建立主板的對象 
     * @return 主板的對象 
     */  
    public MainboardApi createMainboardApi();  
}
  1. 再看看抽象工廠的實現對象,也就是具體的裝機方案對象,先看看裝機方案一的實現,示例代碼以下:
/** 
 * 裝機方案一:Intel 的CPU + 技嘉的主板 
 * 這裏建立CPU和主板對象的時候,是對應的,能匹配上的 
 */  
public class Schema1 implements AbstractFactory{  
    public CPUApi createCPUApi() {  
        return new IntelCPU(1156);  
    }  
    public MainboardApi createMainboardApi() {  
        return new GAMainboard(1156);  
    }    
}  

/** 
 * 裝機方案二:AMD的CPU + 微星的主板 
 * 這裏建立CPU和主板對象的時候,是對應的,能匹配上的 
 */  
public class Schema2 implements AbstractFactory{  
    public CPUApi createCPUApi() {  
        return new AMDCPU(939);  
    }  
    public MainboardApi createMainboardApi() {  
        return new MSIMainboard(939);  
    }    
}
  1. 再來看看裝機工程師類的實現,在如今的實現裏面,裝機工程師至關於使用抽象工廠的客戶端,雖然是由真正的客戶來選擇和建立具體的工廠對象,可是使用抽象工廠的是裝機工程師對象。

裝機工程師類跟前面的實現相比,主要的變化是:從客戶端,再也不傳入選擇CPU和主板的參數,而是直接傳入客戶選擇並建立好的裝機方案對象。這樣就避免了單獨去選擇CPU和主板,客戶要選就是一套,就是一個系列。示例代碼以下:

/** 
 * 裝機工程師的類 
 */  
public  class ComputerEngineer {  
    /** 
     * 定義組裝機器須要的CPU 
     */  
    private CPUApi cpu= null;  
    /** 
     * 定義組裝機器須要的主板 
     */  
    private MainboardApi mainboard = null;  

    /** 
     * 裝機過程 
     * @param schema 客戶選擇的裝機方案 
     */  
    public void makeComputer(AbstractFactory schema){  
        //1:首先準備好裝機所須要的配件  
        prepareHardwares(schema);  
        //2:組裝機器       
        //3:測試機器       
        //4:交付客戶  
    }  
    /** 
     * 準備裝機所須要的配件 
     * @param schema 客戶選擇的裝機方案 
     */  
    private void prepareHardwares(AbstractFactory schema){  
        //這裏要去準備CPU和主板的具體實現,爲了示例簡單,這裏只准備這兩個  
        //但是,裝機工程師並不知道如何去建立,怎麼辦呢?  

        //使用抽象工廠來獲取相應的接口對象  
        this.cpu = schema.createCPUApi();  
        this.mainboard = schema.createMainboardApi();  

        //測試一下配件是否好用  
        this.cpu.calculate();  
        this.mainboard.installCPU();  
    }  
}
  1. 都定義好了,看看客戶端如何使用抽象工廠,示例代碼以下:
public class Client {  
    public static void main(String[] args) {  
        //建立裝機工程師對象  
        ComputerEngineer engineer = new ComputerEngineer();  
        //客戶選擇並建立須要使用的裝機方案對象  
        AbstractFactory schema = new Schema1();  
        //告訴裝機工程師本身選擇的裝機方案,讓裝機工程師組裝電腦  
        engineer.makeComputer(schema);  
    }  
}

如同前面的示例,定義了一個抽象工廠AbstractFactory,在裏面定義了建立CPU和主板對象的接口的方法,可是在抽象工廠裏面,並無指定具體的CPU和主板的實現,也就是無須指定它們具體的實現類。

CPU和主板是相關的對象,是構建電腦的一系列相關配件,這個抽象工廠就至關於一個裝機方案,客戶選擇裝機方案的時候,一選就是一套,CPU和主板是肯定好的,不讓客戶分開選擇,這就避免了出現不匹配的錯誤。

#3 模式講解# ##3.1 認識抽象工廠模式##

  1. 模式的功能

抽象工廠的功能是爲一系列相關對象或相互依賴的對象建立一個接口,必定要注意,這個接口內的方法不是任意堆砌的,而是一系列相關或相互依賴的方法,好比上面例子中的CPU和主板,都是爲了組裝一臺電腦的相關對象。

從某種意義上看,抽象工廠實際上是一個產品系列,或者是產品簇。上面例子中的抽象工廠就能夠當作是電腦簇,每一個不一樣的裝機方案,表明一種具體的電腦系列。

  1. 實現成接口

AbstractFactory在Java中一般實現成爲接口,你們不要被名稱誤導了,覺得是實現成爲抽象類,固然,若是須要爲這個產品簇提供公共的功能,也不是不能夠把AbstractFactory實現成爲抽象類,但通常不這麼作。

  1. 使用工廠方法

AbstractFactory定義了建立產品所須要的接口,具體的實現是在實現類裏面,一般在實現類裏面就須要選擇多種更具體的實現,因此AbstractFactory定義的建立產品的方法能夠當作是工廠方法,而這些工廠方法的具體實現就延遲到了具體的工廠裏面。也就是說使用工廠方法來實現抽象工廠。

  1. 切換產品簇

因爲抽象工廠定義的一系列對象,一般是相關或者相依賴的,這些產品對象就構成了一個產品簇,也就是抽象工廠定義了一個產品簇。這就帶來很是大的靈活性,切換一個產品簇的時候,只要提供不一樣的抽象工廠實現就行了,也就是說如今是以產品簇作爲一個總體被切換。

  1. 抽象工廠模式的調用順序示意圖

輸入圖片說明

##3.2 定義可擴展的工廠## 在前面的示例中,抽象工廠爲每一種它能建立的產品對象都定義了相應的方法,好比建立CPU的方法和建立主板的方法等。

這種實現有一個麻煩,就是若是在產品簇中要新增長一種產品,好比如今要求抽象工廠除了可以建立CPU和主板外,還要可以建立內存對象,那麼就須要在抽象工廠裏面添加建立內存的這麼一個方法。當抽象工廠一發生變化,全部的具體工廠實現都要發生變化,這很是的不靈活。

如今有一種相對靈活,可是不太安全的改進方式來解決這個問題,思路以下:抽象工廠裏面不須要定義那麼多方法,定義一個方法就能夠了,給這個方法設置一個參數,經過這個參數來判斷具體建立什麼產品對象;因爲只有一個方法,在返回類型上就不能是具體的某個產品類型了,只能是全部的產品對象都繼承或者實現的這麼一個類型,好比讓全部的產品都實現某個接口,或者乾脆使用Object類型。

仍是看看代碼來體會一下,把前面那個示例改形成可擴展的工廠實現。

  1. 先來改造抽象工廠,示例代碼以下:
/** 
 * 可擴展的抽象工廠的接口 
 */  
public interface AbstractFactory {  
    /** 
     * 一個通用的建立產品對象的方法,爲了簡單,直接返回Object 
     * 也能夠爲全部被建立的產品定義一個公共的接口 
     * @param type 具體建立的產品類型標識 
     * @return 建立出的產品對象 
     */  
    public Object createProduct(int type);  
}

這裏要特別注意傳入createProduct的參數所表明的含義,這個參數只是用來標識如今是在建立什麼類型的產品,好比標識如今是建立CPU仍是建立主板,通常這個type的含義到此就結束了,再也不進一步表示具體是什麼樣的CPU或具體什麼樣的主板,也就是說type再也不表示具體是建立Intel的CPU仍是建立AMD的CPU,這就是一個參數所表明的含義的深度的問題,要注意雖然也能夠延伸參數的含義到具體的實現上,但這不是可擴展工廠這種設計方式的本意,通常也不這麼作

  1. CPU的接口和實現,主板的接口和實現跟前面的示例是同樣的,就再也不示範了。CPU仍是分紅Intel的CPU和AMD的CPU,主板仍是分紅技嘉的主板和微星的主板。

  2. 下面來提供具體的工廠實現,也就是至關於之前的裝機方案,先改造原來的方案一吧,如今的實現會有較大的變化,示例代碼以下:

/** 
 * 裝機方案一:Intel 的CPU + 技嘉的主板 
 * 這裏建立CPU和主板對象的時候,是對應的,能匹配上的 
 */  
public class Schema1 implements AbstractFactory{  
    public Object createProduct(int type) {  
        Object retObj = null;  
        //type爲1表示建立CPU,type爲2表示建立主板  
        if(type==1){  
            retObj = new IntelCPU(1156);  
        }else if(type==2){  
            retObj = new GAMainboard(1156);  
        }  
        return retObj;  
    }    
} 

/** 
 * 裝機方案二:AMD的CPU + 微星的主板 
 * 這裏建立CPU和主板對象的時候,是對應的,能匹配上的 
 */  
public class Schema2 implements AbstractFactory{  
    public Object createProduct(int type) {  
        Object retObj = null;  
        //type爲1表示建立CPU,type爲2表示建立主板  
        if(type==1){  
            retObj = new AMDCPU(939);  
        }else if(type==2){  
            retObj = new MSIMainboard(939);  
        }  
        return retObj;  
    }    
}
  1. 看看這個時候使用抽象工廠的客戶端實現,也就是在裝機工程師類裏面,經過抽象工廠來獲取相應的配件產品對象,示例代碼以下:
public  class ComputerEngineer {  
    private CPUApi cpu= null;  
    private MainboardApi mainboard = null;  
    public void makeComputer(AbstractFactory schema){  
        prepareHardwares(schema);  
    }  
    private void prepareHardwares(AbstractFactory schema){  
        //這裏要去準備CPU和主板的具體實現,爲了示例簡單,這裏只准備這兩個  
        //但是,裝機工程師並不知道如何去建立,怎麼辦呢?  

        //使用抽象工廠來獲取相應的接口對象  
        this.cpu = (CPUApi)schema.createProduct(1);  
        this.mainboard = (MainboardApi)schema.createProduct(2);  

        //測試一下配件是否好用  
        this.cpu.calculate();  
        this.mainboard.installCPU();  
    }  
}

經過上面的示例,能看到可擴展工廠的基本實現。從客戶端的代碼會發現,爲何說這種方式是不太安全的呢?

你會發現建立產品對象返回來事後,須要造型成爲具體的對象,由於返回的是Object,若是這個時候沒有匹配上,好比返回的不是CPU對象,可是要強制造型成爲CPU,那麼就會發生錯誤,所以這種實現方式的一個潛在缺點就是不太安全。

  1. 接下來,體會一下這種方式的靈活性:

假如如今要加入一個新的產品——內存,固然能夠提供一個新的裝機方案來使用它,這樣已有的代碼就不須要變化了。

先看看內存的接口吧,示例代碼以下:

/** 
 * 內存的接口 
 */  
public interface MemoryApi {  
    /** 
     * 示意方法,內存具備緩存數據的能力 
     */  
    public void cacheData();  
}

提供一個現代內存的基本實現,示例代碼以下:

/** 
 * 現代內存的類 
 */  
public class HyMemory implements MemoryApi{  
    public void cacheData() {  
        System.out.println("如今正在使用現代內存");  
    }  
}

如今想要使用這個新加入的產品,之前實現的代碼都不用變化,只需新添加一個方案,在這個方案裏面使用新的產品,而後客戶端使用這個新的方案便可,示例代碼以下:

/** 
 * 裝機方案三:Intel 的CPU + 技嘉的主板 + 現代的內存 
 */  
public class Scheme3 implements AbstractFactory{  
    public Object createProduct(int type) {  
        Object retObj = null;  
        //type爲1表示建立CPU,type爲2表示建立主板,type爲3表示建立內存  
        if(type==1){  
            retObj = new IntelCPU(1156);  
        }else if(type==2){  
            retObj = new GAMainboard(1156);  
        }  
        //建立新添加的產品  
        else if(type==3){  
            retObj = new HyMemory();  
        }  
        return retObj;  
    }  
}

這個時候的裝機工程師類,若是要建立帶內存的機器,須要在裝機工程師類裏面添加對內存的使用,示例代碼以下:

public  class ComputerEngineer {  
    private CPUApi cpu= null;  
    private MainboardApi mainboard = null;  
    /** 
     * 定義組裝機器須要的內存 
     */  
    private MemoryApi memory = null;  
    public void makeComputer(AbstractFactory schema){  
        prepareHardwares(schema);  
    }  
    private void prepareHardwares(AbstractFactory schema){  
        //使用抽象工廠來獲取相應的接口對象  
        this.cpu = (CPUApi)schema.createProduct(1);  
        this.mainboard = (MainboardApi)schema.createProduct(2);  
        this.memory = (MemoryApi)schema.createProduct(3);  

        //測試一下配件是否好用  
        this.cpu.calculate();  
        this.mainboard.installCPU();  
        if(memory!=null){  
            this.memory.cacheData();  
        }  
    }  
}

##3.3 抽象工廠模式和DAO##

  1. 首先來看看什麼是DAO

DAO:數據訪問對象,是Data Access Object首字母的簡寫。

DAO是JEE(也稱JavaEE,原J2EE)中的一個標準模式,經過它來解決訪問數據對象所面臨的一系列問題,好比:數據源不一樣、存儲類型不一樣、訪問方式不一樣、供應商不一樣、版本不一樣等等,這些不一樣會形成訪問數據的實現上差異很大。

數據源的不一樣,好比存放於數據庫的數據源,存放於LDAP(輕型目錄訪問協議)的數據源;又好比存放於本地的數據源和遠程服務器上的數據源等等

存儲類型的不一樣,好比關係型數據庫(RDBMS)、面向對象數據庫(ODBMS)、純文件、XML等等

訪問方式的不一樣,好比訪問關係型數據庫,能夠用JDBC、EntityBean、JPA等來實現,固然也能夠採用一些流行的框架,如Hibernate、IBatis等等

供應商的不一樣,好比關係型數據庫,流行如Oracel、DB二、SqlServer、MySql等等,它們的供應商是不一樣的

版本不一樣,好比關係型數據庫,不一樣的版本,實現的功能是有差別的,就算是對標準的SQL的支持,也是有差別的

可是對於須要進行數據訪問的邏輯層而言,它可不想面對這麼多不一樣,也不想處理這麼多差別,它但願能以一個統一的方式來訪問數據。此時系統結構如圖所示:

輸入圖片說明

也就是說,DAO須要抽象和封裝全部對數據的訪問,DAO承擔和數據倉庫交互的職責,這也意味着,訪問數據所面臨的全部問題,都須要DAO在內部來自行解決。

  1. DAO和抽象工廠的關係

事實上,在實現DAO模式的時候,最多見的實現策略就是使用工廠的策略,並且可能是經過抽象工廠模式來實現,固然在使用抽象工廠模式來實現的時候,能夠結合工廠方法模式。所以DAO模式和抽象工廠模式有很大的聯繫。

  1. DAO模式的工廠實現策略

(1)採用工廠方法模式

假如如今在一個訂單處理的模塊裏面,你們都知道,訂單一般又分紅兩個部分,一個部分是訂單主記錄或者是訂單主表,另外一個部分是訂單明細記錄或者是訂單子表,那麼如今業務對象須要操做訂單的主記錄,也須要操做訂單的子記錄。

若是這個時候的業務比較簡單,並且對數據的操做是固定的,好比就是操做數據庫,無論訂單的業務如何變化,底層數據存儲都是同樣的,那麼這種狀況下,能夠採用工廠方法模式,此時系統結構如圖所示:

輸入圖片說明

從上面的結構示意圖能夠看出,若是底層存儲固定的時候,DAOFactory就至關於工廠方法模式中的Creator,在裏面定義兩個工廠方法,分別建立訂單主記錄的DAO對象和建立訂單子記錄的DAO對象,由於固定是數據庫實現,所以提供一個具體的工廠RdbDAOFactory(Rdb:關係型數據庫),來實現對象的建立。也就是說DAO能夠採用工廠方法模式來實現。

採用工廠方法模式的狀況,要求DAO底層存儲實現方式是固定的,這種多用在一些簡單的小項目開發上。

(2)採用抽象工廠模式

實際上更多的時候,DAO底層存儲實現方式是不固定的,DAO一般會支持多種存儲實現方式,具體使用哪種存儲方式多是由應用動態決定,或者是經過配置來指定。這種狀況多見於產品開發、或者是稍複雜的應用、或者是較大的項目中。

對於底層存儲方式不固定的時候,通常是採用抽象工廠模式來實現DAO。好比如今的實現除了RDB的實現,還會有Xml的實現,它們會被應用動態的選擇,此時系統結構如圖所示:

輸入圖片說明

從上面的結構示意圖能夠看出,採用抽象工廠模式來實現DAO的時候,DAOFactory就至關於抽象工廠,裏面定義一系列建立相關對象的方法,分別是建立訂單主記錄的DAO對象和建立訂單子記錄的DAO對象,此時OrderMainDAO和OrderDetailDAO就至關於被建立的產品,RdbDAOFactory和XmlDAOFactory就至關於抽象工廠的具體實現,在它們裏面會選擇相應的具體的產品實現來建立對象。

  1. 代碼示例使用抽象工廠實現DAO模式

(1)先看看抽象工廠的代碼實現,示例代碼以下:

/** 
 * 抽象工廠,建立訂單主、子記錄對應的DAO對象 
 */  
public abstract class DAOFactory {  
    /** 
     * 建立訂單主記錄對應的DAO對象 
     * @return 訂單主記錄對應的DAO對象 
     */  
    public abstract OrderMainDAO createOrderMainDAO();  
    /** 
     * 建立訂單子記錄對應的DAO對象 
     * @return 訂單子記錄對應的DAO對象 
     */  
    public abstract OrderDetailDAO createOrderDetailDAO();  
}

(2)看看產品對象的接口,就是訂單主、子記錄的DAO定義,先看訂單主記錄的DAO定義,示例代碼以下:

/** 
 * 訂單主記錄對應的DAO操做接口 
 */  
public interface OrderMainDAO {  
    /** 
     * 示意方法,保存訂單主記錄 
     */  
    public void saveOrderMain();  
}

再看看訂單子記錄的DAO定義,示例代碼以下:

/** 
 * 訂單子記錄對應的DAO操做接口 
 */  
public interface OrderDetailDAO {  
    /** 
     * 示意方法,保存訂單子記錄 
     */  
    public void saveOrderDetail();  
}

(3)接下來實現訂單主、子記錄的DAO,先看關係型數據庫的實現方式,示例代碼以下:

public class RdbMainDAOImpl implements OrderMainDAO{  
    public void saveOrderMain() {  
        System.out.println("now in RdbMainDAOImpl saveOrderMain");  
    }  
}  

public class RdbDetailDAOImpl implements OrderDetailDAO{  
    public void saveOrderDetail() {  
        System.out.println("now in RdbDetailDAOImpl saveOrderDetail");  
    }  
}

Xml實現的方式同樣,爲了演示簡單,都是輸出了一句話,示例代碼以下:

public class XmlMainDAOImpl implements OrderMainDAO{  
    public void saveOrderMain() {  
        System.out.println("now in XmlMainDAOImpl saveOrderMain");  
    }  
}

(4)再看看具體的工廠實現,先看關係型數據庫實現方式的工廠,示例代碼以下:

public class RdbDAOFactory extends DAOFactory{  
    public OrderDetailDAO createOrderDetailDAO() {  
        return new RdbDetailDAOImpl();  
    }  
    public OrderMainDAO createOrderMainDAO() {  
        return new RdbMainDAOImpl();  
    }  
}

Xml實現方式的工廠,示例代碼以下:

public class XmlDAOFactory extends DAOFactory {  
    public OrderDetailDAO createOrderDetailDAO() {  
        return new XmlDetailDAOImpl();  
    }  
    public OrderMainDAO createOrderMainDAO() {  
        return new XmlMainDAOImpl();  
    }  
}

(5)好了,使用抽象工廠來簡單的實現了DAO模式,那麼在客戶端,一般是由業務對象來調用DAO,那麼該怎麼使用這個DAO呢?示例代碼以下:

public class BusinessObject {  
    public static void main(String[] args) {  
        //建立DAO的抽象工廠  
        DAOFactory df = new RdbDAOFactory();  
        //經過抽象工廠來獲取須要的DAO接口  
        OrderMainDAO mainDAO = df.createOrderMainDAO();  
        OrderDetailDAO detailDAO = df.createOrderDetailDAO();  
        //調用DAO來完成數據存儲的功能  
        mainDAO.saveOrderMain();  
        detailDAO.saveOrderDetail();  
    }  
}

經過上面的示例,能夠看出DAO能夠採用抽象工廠模式來實現,這也是大部分DAO實現採用的方式。

##3.4 模式的優缺點##

  1. 分離接口和實現

客戶端使用抽象工廠來建立須要的對象,而客戶端根本就不知道具體的實現是誰,客戶端只是面向產品的接口編程而已,也就是說,客戶端從具體的產品實現中解耦。

  1. 使得切換產品簇變得容易

由於一個具體的工廠實現表明的是一個產品簇,好比上面例子的Scheme1表明裝機方案一:Intel 的CPU + 技嘉的主板,若是要切換成爲Scheme2,那就變成了裝機方案二:AMD的CPU + 微星的主板。

客戶端選用不一樣的工廠實現,就至關因而在切換不一樣的產品簇。

  1. 不太容易擴展新的產品

前面也提到這個問題了,若是須要給整個產品簇添加一個新的產品,那麼就須要修改抽象工廠,這樣就會致使修改全部的工廠實現類。在前面提供了一個能夠擴展工廠的方式來解決這個問題,可是又不夠安全。如何選擇,根據實際應用來權衡吧。

  1. 容易形成類層次複雜

在使用抽象工廠模式的時候,若是須要選擇的層次過多,那麼會形成整個類層次變得複雜。

舉個例子來講,就好比前面講到的那個DAO的示例,如今這個DAO只有一個選擇的層次,也就是選擇一下是使用關係型數據庫來實現,仍是用Xml來實現。如今考慮這樣一種狀況,若是關係型數據庫實現裏面又分紅幾種,好比:基於Oracle的實現,基於SqlServer的實現,基於MySql的實現等等。

那麼客戶端怎麼選呢?不會把全部可能的實現狀況所有都作到一個層次上吧,這個時候客戶端就須要一層一層選擇,也就是整個抽象工廠的實現也須要分出層次來,每一層負責一種選擇,也就是一層屏蔽一種變化,這樣很容易形成複雜的類層次結構。

##3.5 思考抽象工廠模式##

  1. 抽象工廠模式的本質

抽象工廠模式的本質:選擇產品簇的實現。

工廠方法是選擇單個產品的實現,雖然一個類裏面能夠有多個工廠方法,可是這些方法之間通常是沒有聯繫的,即便看起來像有聯繫

可是抽象工廠着重的就是爲一個產品簇選擇實現,定義在抽象工廠裏面的方法一般是有聯繫的,它們都是產品的某一部分或者是相互依賴的。若是抽象工廠裏面只定義一個方法,直接建立產品,那麼就退化成爲工廠方法了。

  1. 什麼時候選用抽象工廠模式

建議在以下狀況中,選用抽象工廠模式:

若是但願一個系統獨立於它的產品的建立,組合和表示的時候,換句話說,但願一個系統只是知道產品的接口,而不關心實現的時候。

若是一個系統要由多個產品系列中的一個來配置的時候,換句話說,就是能夠動態的切換產品簇的時候。

若是要強調一系列相關產品的接口,以便聯合使用它們的時候。

##3.6 相關模式##

  1. 抽象工廠模式和工廠方法模式

這兩個模式既有區別,又有聯繫,能夠組合使用。

工廠方法模式通常是針對單獨的產品對象的建立,而抽象工廠模式注重產品簇對象的建立,這是它們的區別。

若是把抽象工廠建立的產品簇簡化,這個產品簇就只有一個產品,那麼這個時候的抽象工廠跟工廠方法是差很少的,也就是**抽象工廠能夠退化成工廠方法,而工廠方法又能夠退化成簡單工廠,這是它們的聯繫**。

在抽象工廠的實現中,還可使用工廠方法來提供抽象工廠的具體實現,也就是說它們能夠組合使用。

  1. 抽象工廠模式和單例模式

這兩個模式能夠組合使用。

在抽象工廠模式裏面,具體的工廠實現,在整個應用中,一般一個產品系列只須要一個實例就能夠了,所以能夠把具體的工廠實現成爲單例。

相關文章
相關標籤/搜索