【白話設計模式五】工廠方法模式(Factory Method)

#0 系列目錄#算法

#1 場景問題# ##1.1 導出數據的應用框架## 考慮這樣一個實際應用:實現一個導出數據的應用框架,來讓客戶選擇數據的導出方式,並真正執行數據導出。數據庫

在一些實際的企業應用中,一個公司的系統每每分散在不少個不一樣的地方運行,好比各個分公司或者是門市點,公司沒有創建全公司專網的實力,可是又不肯意讓業務數據實時的在廣域網上傳遞,一個是考慮數據安全的問題,一個是運行速度的問題。編程

這種系統一般會有一個折中的方案,那就是各個分公司內運行系統的時候是獨立的,是在本身分公司的局域網內運行。而後在天天業務結束的時候,各個分公司會導出本身的業務數據,而後把業務數據打包經過網絡傳送給總公司,或是專人把數據送到總公司,而後由總公司進行數據導入和核算。設計模式

一般這種系統,在導出數據上,會有一些約定的方式,好比導出成:文本格式、數據庫備份形式、Excel格式、Xml格式等等。api

如今就來考慮實現這樣一個應用框架。在繼續以前,先來了解一些關於框架的知識。安全

##1.2 框架的基礎知識##網絡

  1. 框架是什麼

簡單點說:框架就是能完成必定功能的半成品軟件。架構

就其本質而言,框架是一個軟件,並且是一個半成品的軟件。所謂半成品,就是還不能徹底實現用戶須要的功能,框架只是實現用戶須要的功能的一部分,還須要進一步加工,才能成爲一個知足用戶須要的、完整的軟件。所以框架級的軟件,它的主要客戶是開發人員,而不是最終用戶。框架

有些朋友會想,既然框架只是個半成品,那何須要去學習和使用框架呢?學習成本也不算小,那就是由於框架能完成必定的功能,也就是這「框架已經完成的必定的功能」在吸引着開發人員,讓你們投入去學習和使用框架。學習

  1. 框架能幹什麼

能完成必定功能,加快應用開發進度。

因爲框架完成了必定的功能,並且一般是一些基礎的、有難度的、通用的功能,這就避免咱們在應用開發的時候徹底從頭開始,而是在框架已有的功能之上繼續開發,也就是說會複用框架的功能,從而加快應用的開發進度。

給咱們一個精良的程序架構。框架定義了應用的總體結構,包括類和對象的分割,各部分的主要責任,類和對象怎麼協做,以及控制流程等等。

如今Java界大多數流行的框架,大都出自大師手筆,設計都很精良。基於這樣的框架來開發,通常會遵循框架已經規劃好的結構來進行開發,從而讓咱們開發的應用程序的結構也相對變得精良了。

  1. 對框架的理解

基於框架來開發,事情仍是那些事情,只是看誰作的問題。

對於應用程序和框架的關係,能夠用一個圖來簡單描述一下,如圖所示:

輸入圖片說明

若是沒有框架,那麼客戶要求的全部功能都由開發人員本身來開發,沒問題,一樣能夠實現用戶要求的功能,只是開發人員的工做多點。

若是有了框架,框架自己完成了必定的功能,那麼框架已有的功能,開發人員就能夠不作了,開發人員只須要完成框架沒有的功能,最後一樣是完成客戶要求的全部功能,可是開發人員的工做就減小了。

也就是說,基於框架來開發,軟件要完成的功能並無變化,仍是客戶要求的全部功能,也就是「事情仍是那些事情」的意思。可是有了框架事後,框架完成了一部分功能,而後開發人員再完成一部分功能,最後由框架和開發人員合起來完成了整個軟件的功能,也就是看這些功能「由誰作」的問題。

基於框架開發,能夠不去作框架所作的事情,可是應該明白框架在幹什麼,以及框架是如何實現相應功能的

事實上,在實際開發中,應用程序和框架的關係,一般都不會如上面講述的那樣,分得那麼清楚,更爲廣泛的是相互交互的,也就是應用程序作一部分工做,而後框架作一部分工做,而後應用程序再作一部分工做,而後框架再作一部分工做,如此交錯,最後由應用程序和框架組合起來完成用戶的功能要求。

也用個圖來講明,如圖所示:

輸入圖片說明

若是把這個由應用程序和框架組合在一塊兒構成的矩形,看成最後完成的軟件。試想一下,若是你不懂框架在幹什麼的話,至關於框架對你來說是個黑盒,也就是至關於在上面圖中,去掉框架的兩塊,會發現什麼?沒錯,剩下的應用程序是支離破碎的,是相互分隔開來的。

這會致使一個很是致命的問題,整個應用是如何運轉起來的,你是不清楚的,也就是說對你而言,項目已經失控了,從項目管理的角度來說,這是很危險的。

所以,在基於框架開發的時候,雖然咱們能夠不去作框架所作的事情,可是應該搞明白框架在幹什麼,若是條件許可的話,還應該搞清楚框架是如何實現相應功能的,至少應該把大體的實現思路和實現步驟搞清楚,這樣咱們才能總體的掌控整個項目,才能儘可能減小出現項目失控的狀況。

  1. 框架和設計模式的關係

設計模式比框架更抽象。

框架已是實現出來的軟件了,雖然只是個半成品的軟件,但畢竟是已經實現出來的了。而設計模式的重心還在於解決問題的方案上,也就是還停留在思想的層面。所以設計模式比框架更爲抽象

設計模式是比框架更小的體系結構元素。

如上所述,框架是已經實現出來的軟件,並實現了一系列的功能,所以一個框架,一般會包含多個設計模式的應用。

框架比設計模式更加特例化。

框架是完成必定功能的半成品軟件,也就是說,框架的目的很明確,就是要解決某一個領域的某些問題,那是很具體的功能,不一樣的領域實現出來的框架是不同的。

而設計模式還停留在思想的層面,在不一樣的領域均可以應用,只要相應的問題適合用某個設計模式來解決。所以框架老是針對特定領域的,而設計模式更加註重從思想上,從方法上來解決問題,更加通用化。

##1.3 有何問題## 分析上面要實現的應用框架,無論用戶選擇什麼樣的導出格式,最後導出的都是一個文件,並且系統並不知道究竟要導出成爲何樣的文件,所以應該有一個統一的接口,來描述系統最後生成的對象,並操做輸出的文件

先把導出的文件對象的接口定義出來,示例代碼以下:

/**
 * 導出的文件對象的接口
 */
public interface ExportFileApi {
   /**
    * 導出內容成爲文件
    * @param data 示意:須要保存的數據
    * @return 是否導出成功
    */
   public boolean export(String data);
}

對於實現導出數據的業務功能對象,它應該根據須要來建立相應的ExportFileApi的實現對象,由於特定的ExportFileApi的實現是與具體的業務相關的。可是對於實現導出數據的業務功能對象而言,它並不知道應該建立哪個ExportFileApi的實現對象,也不知道如何建立。

也就是說:**對於實現導出數據的業務功能對象,它須要建立ExportFileApi的具體實例對象,可是它只知道ExportFileApi接口,而不知道其具體的實現。**那該怎麼辦呢?

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

  1. 工廠方法模式定義

定義一個用於建立對象的接口,讓子類決定實例化哪個類,Factory Method使一個類的實例化延遲到其子類。

  1. 應用工廠方法模式來解決的思路

仔細分析上面的問題,事實上在實現導出數據的業務功能對象裏面,根本就不知道究竟要使用哪種導出文件的格式,所以這個對象本就不該該和具體的導出文件的對象耦合在一塊兒,它只須要面向導出的文件對象的接口就行了

可是這樣一來,又有新的問題產生了:接口是不能直接使用的,須要使用具體的接口實現對象的實例

這不是自相矛盾嗎?要求面向接口,不讓和具體的實現耦合,可是又須要建立接口的具體實現對象的實例。怎麼解決這個矛盾呢?

工廠方法模式的解決思路頗有意思,那就是不解決,採起無爲而治的方式:不是須要接口對象嗎,那就定義一個方法來建立;但是事實上它本身是不知道如何建立這個接口對象的,沒有關係,那就定義成抽象方法就行了,本身實現不了,那就讓子類來實現,這樣這個對象自己就能夠只是面向接口編程,而無需關心到底如何建立接口對象了

##2.2 模式結構和說明## 工廠方法模式的結構如圖所示:

輸入圖片說明

Product:定義工廠方法所建立的對象的接口,也就是實際須要使用的對象的接口。

ConcreteProduct:具體的Product接口的實現對象。

Creator:建立器,聲明工廠方法,工廠方法一般會返回一個Product類型的實例對象,並且可能是抽象方法。也能夠在Creator裏面提供工廠方法的默認實現,讓工廠方法返回一個缺省的Product類型的實例對象。

ConcreteCreator:具體的建立器對象,覆蓋實現Creator定義的工廠方法,返回具體的Product實例。

##2.3 工廠方法模式示例代碼##

  1. 先看看Product的定義,示例代碼以下:
/**
 * 工廠方法所建立的對象的接口
 */
public interface Product {
    //能夠定義Product的屬性和方法
}
  1. 再看看具體的Product的實現對象,示例代碼以下:
/**
 * 具體的Product對象
 */
public class ConcreteProduct implements Product {
    //實現Product要求的方法
}
  1. 接下來看看建立器的定義,示例代碼以下:
/**
 * 建立器,聲明工廠方法
 */
public abstract class Creator {
    /**
     * 建立Product的工廠方法
     * @return Product對象
     */
    protected abstract Product factoryMethod();
    /**
     * 示意方法,實現某些功能的方法
     */
    public void someOperation() {
        //一般在這些方法實現中,須要調用工廠方法來獲取Product對象
        Product product = factoryMethod();
    }
}
  1. 再看看具體的建立器實現對象,示例代碼以下:
/**
 * 具體的建立器實現對象
 */
public class ConcreteCreator extends Creator {
    protected Product factoryMethod() {
        //重定義工廠方法,返回一個具體的Product對象
        return new ConcreteProduct();
    }
}

##2.4 使用工廠方法模式來實現示例## 要使用工廠方法模式來實現示例,先來按照工廠方法模式的結構,對應出哪些是被建立的Product,哪些是Creator。分析要求實現的功能,導出的文件對象接口ExportFileApi就至關因而Product,而用來實現導出數據的業務功能對象就至關於Creator。把Product和Creator分開事後,就能夠分別來實現它們了。

使用工廠模式來實現示例的程序結構如圖所示:

輸入圖片說明

  1. 導出的文件對象接口ExportFileApi的實現沒有變化,這裏就不去贅述了

  2. **接下來看看接口ExportFileApi的實現,爲了示例簡單,只實現導出文本文件格式和數據庫備份文件兩種。先看看導出文本文件格式的實現,示例代碼以下: **

/**
 * 導出成文本文件格式的對象
 */
public class ExportTxtFile implements ExportFileApi{
    public boolean export(String data) {
        //簡單示意一下,這裏須要操做文件
        System.out.println("導出數據"+data+"到文本文件");
        return true;
    }
}

/**
 * 導出成數據庫備份文件形式的對象
 */
public class ExportDB implements ExportFileApi{
    public boolean export(String data) {
        //簡單示意一下,這裏須要操做數據庫和文件
        System.out.println("導出數據"+data+"到數據庫備份文件");
        return true;
    }
}
  1. Creator這邊的實現,首先看看ExportOperate的實現,示例代碼以下:
/**
 * 實現導出數據的業務功能對象
 */
public abstract class ExportOperate {
    /**
     * 導出文件
     * @param data 須要保存的數據
     * @return 是否成功導出文件
     */
    public boolean export(String data){
        //使用工廠方法
        ExportFileApi api = factoryMethod();
        return api.export(data);
    }
    /**
     * 工廠方法,建立導出的文件對象的接口對象
     * @return 導出的文件對象的接口對象
     */
    protected abstract ExportFileApi factoryMethod();
}
  1. 加入了兩個Creator實現,示例代碼以下:
/**
 * 具體的建立器實現對象,實現建立導出成文本文件格式的對象
 */
public class ExportTxtFileOperate extends ExportOperate{
    protected ExportFileApi factoryMethod() {
        //建立導出成文本文件格式的對象
        return new ExportTxtFile();
    }
}

/**
 * 具體的建立器實現對象,實現建立導出成數據庫備份文件形式的對象
 */
public class ExportDBOperate extends ExportOperate{
    protected ExportFileApi factoryMethod() {
        //建立導出成數據庫備份文件形式的對象
        return new ExportDB();
    }
}
  1. 客戶端直接建立須要使用的Creator對象,而後調用相應的功能方法,示例代碼以下:
public class Client {
    public static void main(String[] args) {
        //建立須要使用的Creator對象
        ExportOperate operate = new ExportDBOperate();
        //調用輸出數據的功能方法
        operate.export("測試數據");
    }
}

#3 模式講解# ##3.1 認識工廠方法模式##

  1. 模式的功能

工廠方法的主要功能是讓父類在不知道具體實現的狀況下,完成自身的功能調用,而具體的實現延遲到子類來實現。

這樣在設計的時候,不用去考慮具體的實現,須要某個對象,把它經過工廠方法返回就行了,在使用這些對象實現功能的時候仍是經過接口來操做,這很是相似於IoC/DI的思想,這個在後面給你們稍詳細點介紹一下。

  1. 實現成抽象類

工廠方法的實現中,一般父類會是一個抽象類,裏面包含建立所需對象的抽象方法,這些抽象方法就是工廠方法。

這裏要注意一個問題,子類在實現這些抽象方法的時候,一般並非真的由子類來實現具體的功能,而是在子類的方法裏面作選擇,選擇具體的產品實現對象

父類裏面,一般會有使用這些產品對象來實現必定的功能的方法,並且這些方法所實現的功能一般都是公共的功能,無論子類選擇了何種具體的產品實現,這些方法的功能老是能正確執行。

  1. 實現成具體的類

固然也能夠把父類實現成爲一個具體的類,這種狀況下,一般是在父類中提供獲取所需對象的默認實現方法,這樣就算沒有具體的子類,也可以運行

一般這種狀況仍是須要具體的子類來決定具體要如何建立父類所須要的對象。也把這種狀況稱爲工廠方法爲子類提供了掛鉤,經過工廠方法,可讓子類對象來覆蓋父類的實現,從而提供更好的靈活性。

  1. 工廠方法的參數和返回

工廠方法的實現中,可能須要參數,以便決定到底選用哪種具體的實現。也就是說經過在抽象方法裏面傳遞參數,在子類實現的時候根據參數進行選擇,看看究竟應該建立哪個具體的實現對象

通常工廠方法返回的是被建立對象的接口對象,固然也能夠是抽象類或者一個具體的類的實例。

  1. 誰來使用工廠方法建立的對象

這裏首先要搞明白一件事情,就是誰在使用工廠方法建立的對象?

事實上,在工廠方法模式裏面,應該是Creator中的其它方法在使用工廠方法建立的對象,雖然也能夠把工廠方法建立的對象直接提供給Creator外部使用,但工廠方法模式的本意,是由Creator對象內部的方法來使用工廠方法建立的對象,也就是說,工廠方法通常不提供給Creator外部使用。

客戶端應該是使用Creator對象,或者是使用由Creator建立出來的對象。對於客戶端使用Creator對象,這個時候工廠方法建立的對象,是Creator中的某些方法使用。對於使用那些由Creator建立出來的對象,這個時候工廠方法建立的對象,是構成客戶端須要的對象的一部分。分別舉例來講明。

① 客戶端使用Creator對象的狀況

好比前面的示例,對於「實現導出數據的業務功能對象」的類ExportOperate,它有一個export的方法,在這個方法裏面,須要使用具體的「導出的文件對象的接口對象」 ExportFileApi,而ExportOperate是不知道具體的ExportFileApi實現的,那麼怎麼作的呢?就是定義了一個工廠方法,用來返回ExportFileApi的對象,而後export方法會使用這個工廠方法來獲取它所須要的對象,而後執行功能

這個時候的客戶端是怎麼作的呢?這個時候客戶端主要就是使用這個ExportOperate的實例來完成它想要完成的功能,也就是客戶端使用Creator對象的狀況,簡單描述這種狀況下的代碼結構以下:

/**
 * 客戶端使用Creator對象的狀況下,Creator的基本實現結構
 */
public abstract class Creator {
    /**
     * 工廠方法,通常不對外
     * @return 建立的產品對象
     */
    protected abstract Product factoryMethod();
    /**
     * 提供給外部使用的方法,
     * 客戶端通常使用Creator提供的這些方法來完成所須要的功能
     */
    public void someOperation(){
        //在這裏使用工廠方法
        Product p = factoryMethod();
    }
}

② 客戶端使用由Creator建立出來的對象

另一種是由Creator向客戶端返回由「工廠方法建立的對象」來構建的對象,這個時候工廠方法建立的對象,是構成客戶端須要的對象的一部分。簡單描述這種狀況下的代碼結構以下:

/**
 * 客戶端使用Creator來建立客戶端須要的對象的狀況下,Creator的基本實現結構
 */
public abstract class Creator {
    /**
     * 工廠方法,通常不對外,建立一個部件對象
     * @return 建立的產品對象,通常是另外一個產品對象的部件
     */
    protected abstract Product1 factoryMethod1();
    /**
     * 工廠方法,通常不對外,建立一個部件對象
     * @return 建立的產品對象,通常是另外一個產品對象的部件
     */
    protected abstract Product2 factoryMethod2();
    /**
     * 建立客戶端須要的對象,客戶端主要使用產品對象來完成所須要的功能
     * @return 客戶端須要的對象
     */
    public Product createProduct(){
        //在這裏使用工廠方法,獲得客戶端所需對象的部件對象
        Product1 p1 = factoryMethod1();
        Product2 p2 = factoryMethod2();
        //工廠方法建立的對象是建立客戶端對象所須要的
        Product p = new ConcreteProduct();
        p.setProduct1(p1);
        p.setProduct2(p2);
    
        return p;
    }
}

小結一下:在工廠方法模式裏面,客戶端要麼使用Creator對象,要麼使用Creator建立的對象,通常客戶端不直接使用工廠方法。固然也能夠直接把工廠方法暴露給客戶端操做,可是通常不這麼作

  1. 工廠方法模式的調用順序示意圖

因爲客戶端使用Creator對象有兩種典型的狀況,所以調用的順序示意圖也分作兩種狀況,先看看客戶端使用由Creator建立出來的對象狀況的調用順序示意圖,如圖所示:

輸入圖片說明

接下來看看客戶端使用Creator對象時候的調用順序示意圖,如圖所示:

輸入圖片說明

##3.2 工廠方法模式與IoC/DI##

IoC——Inversion of Control 控制反轉

DI——Dependency Injection 依賴注入

  1. 如何理解IoC/DI

要想理解上面兩個概念,就必須搞清楚以下的問題:

參與者都有誰?

依賴:誰依賴於誰?爲何須要依賴?

注入:誰注入於誰?到底注入什麼?

控制反轉:誰控制誰?控制什麼?爲什麼叫反轉(有反轉就應該有正轉了)?

依賴注入和控制反轉是同一律念嗎?

(1) 參與者都有誰:

通常有三方參與者,一個是某個對象;一個是IoC/DI的容器;另外一個是某個對象的外部資源。

又要名詞解釋一下,某個對象指的就是任意的、普通的Java對象; IoC/DI的容器簡單點說就是指用來實現IoC/DI功能的一個框架程序;對象的外部資源指的就是對象須要的,可是是從對象外部獲取的,都統稱資源,好比:對象須要的其它對象、或者是對象須要的文件資源等等。

(2) 誰依賴於誰:固然是某個對象依賴於IoC/DI的容器

(3) 爲何須要依賴:對象須要IoC/DI的容器來提供對象須要的外部資源

(4) 誰注入於誰:很明顯是IoC/DI的容器 注入 某個對象

(5) 到底注入什麼:就是注入某個對象所須要的外部資源

(6) 誰控制誰:固然是IoC/DI的容器來控制對象了

(7) 控制什麼:主要是控制對象實例的建立

(8) 爲什麼叫反轉:

反轉是相對於正向而言的,那麼什麼算是正向的呢?考慮一下常規狀況下的應用程序,若是要在A裏面使用C,你會怎麼作呢?固然是直接去建立C的對象,也就是說,是在A類中主動去獲取所須要的外部資源C,這種狀況被稱爲正向的。那麼什麼是反向呢?就是A類再也不主動去獲取C,而是被動等待,等待IoC/DI的容器獲取一個C的實例,而後反向的注入到A類中。

用圖例來講明一下,先看沒有IoC/DI的時候,常規的A類使用C類的示意圖,如圖所示:

輸入圖片說明

當有了IoC/DI的容器後,A類再也不主動去建立C了,如圖所示:

輸入圖片說明

而是被動等待,等待IoC/DI的容器獲取一個C的實例,而後反向的注入到A類中,如圖所示:

輸入圖片說明

(9) 依賴注入和控制反轉是同一律念嗎?

根據上面的講述,應該能看出來,依賴注入和控制反轉是對同一件事情的不一樣描述,從某個方面講,就是它們描述的角度不一樣。依賴注入是從應用程序的角度在描述,能夠把依賴注入描述完整點:應用程序依賴容器建立並注入它所須要的外部資源;而控制反轉是從容器的角度在描述,描述完整點:容器控制應用程序,由容器反向的嚮應用程序注入應用程序所須要的外部資源。

(10) 小結一下:

其實IoC/DI對編程帶來的最大改變不是從代碼上,而是從思想上,發生了「主從換位」的變化。應用程序本來是老大,要獲取什麼資源都是主動出擊,可是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC/DI容器來建立並注入它所須要的資源了。

這麼小小的一個改變實際上是編程思想的一個大進步,這樣就有效的分離了對象和它所須要的外部資源,使得它們鬆散耦合,有利於功能複用,更重要的是使得程序的整個體系結構變得很是靈活。

  1. 工廠方法模式和IoC/DI有什麼關係呢?

從某個角度講,它們的思想很相似。

上面講了,有了IoC/DI事後,應用程序就再也不主動了,而是被動等待由容器來注入資源,那麼在編寫代碼的時候,一旦要用到外部資源,就會開一個窗口,讓容器能注入進來,也就是提供給容器使用的注入的途徑,固然這不是咱們的重點,就不去細細講了,用setter注入來示例一下,看看使用IoC/DI的代碼是什麼樣子,示例代碼以下:

public class A {
    /**
     * 等待被注入進來
     */
    private C c = null;
    /**
     * 注入資源C的方法
     * @param c 被注入的資源
     */
    public void setC(C c){
        this.c = c;
    }
    public void t1(){
        //這裏須要使用C,但是又不讓主動去建立C了,怎麼辦?
        //反正就要求從外部注入,這樣更省心,
        //本身不用管怎麼獲取C,直接使用就行了
        c.tc();
    }
}

接口C的示例代碼以下:

public interface C {
    public void tc();
}

從上面的示例代碼能夠看出,如今在A裏面寫代碼的時候,凡是碰到了須要外部資源,那麼就提供注入的途徑,要求從外部注入,本身只管使用這些對象。

public abstract class A1 {
    /**
     * 工廠方法,建立C1,相似於從子類注入進來的途徑
     * @return C1的對象實例
     */
    protected abstract C1 createC1();
    public void t1(){
        //這裏須要使用C1類,但是不知道到底是用哪個
        //也就不主動去建立C1了,怎麼辦?
        //反正會在子類裏面實現,這裏不用管怎麼獲取C1,直接使用就行了
        createC1().tc();
    }
}

子類的示例代碼以下:

public class A2 extends A1 {
    protected C1 createC1() {
        //真正的選擇具體實現,並建立對象
        return new C2();
    }
}

C1接口和前面C接口是同樣的,C2這個實現類也是空的,只是演示一下,所以就不去展現它們的代碼了。

仔細體會上面的示例,對比它們的實現,尤爲是從思想層面上,會發現工廠方法模式和IoC/DI的思想是類似的,都是「主動變被動」,進行了「主從換位」,從而得到了更靈活的程序結構。

##3.3 平行的類層次結構##

  1. 什麼是平行的類層次結構呢?

簡單點說,假若有兩個類層次結構,其中一個類層次中的每一個類在另外一個類層次中都有一個對應的類的結構,就被稱爲平行的類層次結構。

舉個例子來講,硬盤對象有不少種,如分紅臺式機硬盤和筆記本硬盤,在臺式機硬盤的具體實現上面,又有希捷、西數等不一樣品牌的實現,一樣在筆記本硬盤上,也有希捷、日立、IBM等不一樣品牌的實現;硬盤對象具備本身的行爲,如硬盤能存儲數據,也能從硬盤上獲取數據,不一樣的硬盤對象對應的行爲對象是不同的,由於不一樣的硬盤對象,它的行爲的實現方式是不同的。若是把硬盤對象和硬盤對象的行爲分開描述,那麼就構成了如圖所示的結構:

輸入圖片說明

硬盤對象是一個類層次,硬盤的行爲這邊也是一個類層次,並且兩個類層次中的類是對應的。臺式機西捷硬盤對象就對應着硬盤行爲裏面的臺式機西捷硬盤的行爲;筆記本IBM硬盤就對應着筆記本IBM硬盤的行爲,這就是一種典型的平行的類層次結構。

這種平行的類層次結構用來幹什麼呢?主要用來把一個類層次中的某些行爲分離出來,讓類層次中的類把本來屬於本身的職責,委託給分離出來的類去實現,從而使得類層次自己變得更簡單,更容易擴展和複用。

通常來說,分離出去的這些類的行爲,會對應着類層次結構來組織,從而造成一個新的類層次結構,至關於原來對象的行爲的這麼一個類層次結構,而這個層次結構和原來的類層次結構是存在對應關係的,所以被稱爲平行的類層次結構。

  1. 工廠方法模式跟平行的類層次結構有何關係呢?

可使用工廠方法模式來鏈接平行的類層次。

看上面的示例圖,在每一個硬盤對象裏面,都有一個工廠方法createHDOperate,經過這個工廠方法,客戶端就能夠獲取一個跟硬盤對象相對應的行爲對象。在硬盤對象的子類裏面,會覆蓋父類的工廠方法createHDOperate,以提供跟自身相對應的行爲對象,從而天然的把兩個平行的類層次鏈接起來使用。

##3.4 參數化工廠方法## 所謂參數化工廠方法指的就是:經過給工廠方法傳遞參數,讓工廠方法根據參數的不一樣來建立不一樣的產品對象,這種狀況就被稱爲參數化工廠方法。固然工廠方法建立的不一樣的產品必須是同一個Product類型的。

來改造前面的示例,如今有一個工廠方法來建立ExportFileApi這個產品的對象,可是ExportFileApi接口的具體實現不少,爲了方便建立的選擇,直接從客戶端傳入一個參數,這樣在須要建立ExportFileApi對象的時候,就把這個參數傳遞給工廠方法,讓工廠方法來實例化具體的ExportFileApi實現對象。

  1. 先來看Product的接口,就是ExportFileApi接口,跟前面的示例沒有任何變化,爲了方便你們查看,這裏重複一下,示例代碼以下:
/**
 * 導出的文件對象的接口
 */
public interface ExportFileApi {
    /**
     * 導出內容成爲文件
     * @param data 示意:須要保存的數據
     * @return 是否導出成功
     */
    public boolean export(String data); 
}
  1. 一樣提供保存成文本文件和保存成數據庫備份文件的實現,跟前面的示例沒有任何變化,示例代碼以下:
public class ExportTxtFile implements ExportFileApi{
    public boolean export(String data) {
        //簡單示意一下,這裏須要操做文件
        System.out.println("導出數據"+data+"到文本文件");
        return true;
    }
}
public class ExportDB implements ExportFileApi{
    public boolean export(String data) {
        //簡單示意一下,這裏須要操做數據庫和文件
        System.out.println("導出數據"+data+"到數據庫備份文件");
        return true;
    }
}
  1. 接下來該看看ExportOperate類了,這個類的變化大體以下:

ExportOperate類中的建立產品的工廠方法,一般須要提供默認的實現,不抽象了,也就是變成正常方法。

ExportOperate類也再也不定義成抽象類了,由於有了默認的實現,客戶端可能須要直接使用這個對象。

設置一個導出類型的參數,經過export方法從客戶端傳入。

/**
 * 實現導出數據的業務功能對象
 */
public class ExportOperate {
    /**
     * 導出文件
     * @param type 用戶選擇的導出類型
     * @param data 須要保存的數據
     * @return 是否成功導出文件
     */
    public boolean export(int type,String data){
        //使用工廠方法
        ExportFileApi api = factoryMethod(type);
        return api.export(data);
    }
    /**
     * 工廠方法,建立導出的文件對象的接口對象
     * @param type 用戶選擇的導出類型
     * @return 導出的文件對象的接口對象
     */
    protected ExportFileApi factoryMethod(int type){
        ExportFileApi api = null;
        //根據類型來選擇究竟要建立哪種導出文件對象
        if(type==1){
            api = new ExportTxtFile();
        }else if(type==2){
            api = new ExportDB();
       }
       return api;
    }
}
  1. 此時的客戶端,很是簡單,直接使用ExportOperate類,示例代碼以下:
public class Client {
    public static void main(String[] args) {
        //建立須要使用的Creator對象
        ExportOperate operate = new ExportOperate();
        //調用輸出數據的功能方法,傳入選擇處處類型的參數
        operate.export(1,"測試數據");
    }
}

測試看看,而後修改一下客戶端的參數,體會一下經過參數來選擇具體的導出實現的過程。這是一種很常見的參數化工廠方法的實現方式,可是也仍是有把參數化工廠方法實現成爲抽象的,這點要注意,並非說參數化工廠方法就不能實現成爲抽象類了。只是通常狀況下,參數化工廠方法,在父類都會提供默認的實現

  1. 擴展新的實現

使用參數化工廠方法,擴展起來會很是容易,已有的代碼都不會改變,只要新加入一個子類來提供新的工廠方法實現,而後在客戶端使用這個新的子類便可。

這種實現方式還有一個有意思的功能,就是子類能夠選擇性覆蓋,不想覆蓋的功能還能夠返回去讓父類來實現,頗有意思。

先擴展一個導出成xml文件的實現,試試看,示例代碼以下:

/**
 * 導出成xml文件的對象
 */
public class ExportXml implements ExportFileApi{
    public boolean export(String data) {
        //簡單示意一下
        System.out.println("導出數據"+data+"到XML文件");
        return true;
    }
}

而後擴展ExportOperate類,來加入新的實現,示例代碼以下:

/**
 * 擴展ExportOperate對象,加入能夠導出XML文件
 */
public class ExportOperate2 extends ExportOperate{
    /**
     * 覆蓋父類的工廠方法,建立導出的文件對象的接口對象
     * @param type 用戶選擇的導出類型
     * @return 導出的文件對象的接口對象
     */
    protected ExportFileApi factoryMethod(int type){
        ExportFileApi api = null;
        //能夠所有覆蓋,也能夠選擇本身感興趣的覆蓋,
        //這裏只想添加本身新的實現,其它的無論
        if(type==3){
            api = new ExportXml();
        }else{
            //其它的仍是讓父類來實現
            api = super.factoryMethod(type);
        }
        return api;
    }
}

看看此時的客戶端,也很是簡單,只是在變換傳入的參數,示例代碼以下:

public class Client {
    public static void main(String[] args) {
        //建立須要使用的Creator對象
        ExportOperate operate = new ExportOperate2();
        //下面變換傳入的參數來測試參數化工廠方法
        operate.export(1,"Test1");
        operate.export(2,"Test2");
        operate.export(3,"Test3");
    }
}

##3.5 工廠方法模式的優缺點##

  1. 能夠在不知具體實現的狀況下編程

工廠方法模式可讓你在實現功能的時候,若是須要某個產品對象,只須要使用產品的接口便可,而無需關心具體的實現。選擇具體實現的任務延遲到子類去完成。 更容易擴展對象的新版本。

工廠方法給子類提供了一個掛鉤,使得擴展新的對象版本變得很是容易。好比上面示例的參數化工廠方法實現中,擴展一個新的導出Xml文件格式的實現,已有的代碼都不會改變,只要新加入一個子類來提供新的工廠方法實現,而後在客戶端使用這個新的子類便可。

另外這裏提到的掛鉤,就是咱們常常說的鉤子方法(hook),這個會在後面講模板方法模式的時候詳細點說明。

  1. 鏈接平行的類層次

工廠方法除了創造產品對象外,在鏈接平行的類層次上也大顯身手。這個在前面已經詳細講述了。

  1. 具體產品對象和工廠方法的耦合性

在工廠方法模式裏面,工廠方法是須要建立產品對象的,也就是須要選擇具體的產品對象,並建立它們的實例,所以具體產品對象和工廠方法是耦合的。

##3.6 思考工廠方法模式##

  1. 工廠方法模式的本質

工廠方法模式的本質:延遲到子類來選擇實現。

仔細體會前面的示例,你會發現,工廠方法模式中的工廠方法,在真正實現的時候,通常是先選擇具體使用哪個具體的產品實現對象,而後建立這個具體產品對象的示例,而後就能夠返回去了。也就是說,工廠方法自己並不會去實現產品接口,具體的產品實現是已經寫好了的,工廠方法只要去選擇實現就行了。

有些朋友可能會說,這不是跟簡單工廠同樣嗎?

確實從本質上講,它們是很是相似的,具體實現上都是在「選擇實現」。可是也存在不一樣點,簡單工廠是直接在工廠類裏面進行「選擇實現」;而工廠方法會把這個工做延遲到子類來實現,工廠類裏面使用工廠方法的地方是依賴於抽象而不是具體的實現,從而使得系統更加靈活,具備更好的可維護性和可擴展性。

其實若是把工廠模式中的Creator退化一下,只提供工廠方法,並且這些工廠方法還都提供默認的實現,那不就變成了簡單工廠了嗎?好比把剛纔示範參數化工廠方法的例子代碼拿過來再簡化一下,你就能看出來,寫得跟簡單工廠是差很少的,示例代碼以下:

輸入圖片說明

看完上述代碼,會體會到簡單工廠和工廠方法模式是有很大類似性的了吧,從某個角度來說,能夠認爲簡單工廠就是工廠方法模式的一種特例,所以它們的本質是相似的,也就不足爲奇了

  1. 對設計原則的體現

工廠方法模式很好的體現了「依賴倒置原則」。

依賴倒置原則告訴咱們「要依賴抽象,不要依賴於具體類」,簡單點說就是:不能讓高層組件依賴於低層組件,並且無論高層組件仍是低層組件,都應該依賴於抽象。

好比前面的示例,實現客戶端請求操做的ExportOperate就是高層組件;而具體實現數據導出的對象就是低層組件,好比ExportTxtFile、ExportDB;而ExportFileApi接口就至關因而那個抽象。

對於ExportOperate來講,它不關心具體的實現方式,它只是「面向接口編程」;對於具體的實現來講,它只關心本身「如何實現接口」所要求的功能。

那麼倒置的是什麼呢?倒置的是這個接口的「全部權」。事實上,ExportFileApi接口中定義的功能,都是由高層組件ExportOperate來提出的要求,也就是說接口中的功能,是高層組件須要的功能。可是高層組件只是提出要求,並不關心如何實現,而低層組件,就是來真正實現高層組件所要求的接口功能的。所以看起來,低層實現的接口的全部權並不在底層組件手中,而是倒置到高層組件去了

  1. 什麼時候選用工廠方法模式

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

若是一個類須要建立某個接口的對象,可是又不知道具體的實現,這種狀況能夠選用工廠方法模式,把建立對象的工做延遲到子類去實現

若是一個類自己就但願,由它的子類來建立所需的對象的時候,應該使用工廠方法模式

##3.7 相關模式##

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

這兩個模式能夠組合使用,具體的放到抽象工廠模式中去講。

  1. 工廠方法模式和模板方法模式

這兩個模式外觀相似,都是有一個抽象類,而後由子類來提供一些實現,可是工廠方法模式的子類專一的是建立產品對象,而模板方法模式的子類專一的是爲固定的算法骨架提供某些步驟的實現

這兩個模式能夠組合使用,一般在模板方法模式裏面,使用工廠方法來建立模板方法須要的對象。

相關文章
相關標籤/搜索