1 場(chǎng)景問(wèn)題#
1.1 導(dǎo)出數(shù)據(jù)的應(yīng)用框架##
考慮這樣一個(gè)實(shí)際應(yīng)用:實(shí)現(xiàn)一個(gè)導(dǎo)出數(shù)據(jù)的應(yīng)用框架,來(lái)讓客戶選擇數(shù)據(jù)的導(dǎo)出方式,并真正執(zhí)行數(shù)據(jù)導(dǎo)出。
在一些實(shí)際的企業(yè)應(yīng)用中,一個(gè)公司的系統(tǒng)往往分散在很多個(gè)不同的地方運(yùn)行,比如各個(gè)分公司或者是門(mén)市點(diǎn),公司沒(méi)有建立全公司專網(wǎng)的實(shí)力,但是又不愿意讓業(yè)務(wù)數(shù)據(jù)實(shí)時(shí)的在廣域網(wǎng)上傳遞,一個(gè)是考慮數(shù)據(jù)安全的問(wèn)題,一個(gè)是運(yùn)行速度的問(wèn)題。
這種系統(tǒng)通常會(huì)有一個(gè)折中的方案,那就是各個(gè)分公司內(nèi)運(yùn)行系統(tǒng)的時(shí)候是獨(dú)立的,是在自己分公司的局域網(wǎng)內(nèi)運(yùn)行。然后在每天業(yè)務(wù)結(jié)束的時(shí)候,各個(gè)分公司會(huì)導(dǎo)出自己的業(yè)務(wù)數(shù)據(jù),然后把業(yè)務(wù)數(shù)據(jù)打包通過(guò)網(wǎng)絡(luò)傳送給總公司,或是專人把數(shù)據(jù)送到總公司,然后由總公司進(jìn)行數(shù)據(jù)導(dǎo)入和核算。
通常這種系統(tǒng),在導(dǎo)出數(shù)據(jù)上,會(huì)有一些約定的方式,比如導(dǎo)出成:文本格式、數(shù)據(jù)庫(kù)備份形式、Excel格式、Xml格式等等。
現(xiàn)在就來(lái)考慮實(shí)現(xiàn)這樣一個(gè)應(yīng)用框架。在繼續(xù)之前,先來(lái)了解一些關(guān)于框架的知識(shí)。
1.2 框架的基礎(chǔ)知識(shí)##
- 框架是什么
簡(jiǎn)單點(diǎn)說(shuō):框架就是能完成一定功能的半成品軟件。
就其本質(zhì)而言,框架是一個(gè)軟件,而且是一個(gè)半成品的軟件。所謂半成品,就是還不能完全實(shí)現(xiàn)用戶需要的功能,框架只是實(shí)現(xiàn)用戶需要的功能的一部分,還需要進(jìn)一步加工,才能成為一個(gè)滿足用戶需要的、完整的軟件。因此框架級(jí)的軟件,它的主要客戶是開(kāi)發(fā)人員,而不是最終用戶。
有些朋友會(huì)想,既然框架只是個(gè)半成品,那何必要去學(xué)習(xí)和使用框架呢?學(xué)習(xí)成本也不算小,那就是因?yàn)榭蚣苣芡瓿梢欢ǖ墓δ埽簿褪沁@“框架已經(jīng)完成的一定的功能”在吸引著開(kāi)發(fā)人員,讓大家投入去學(xué)習(xí)和使用框架。
- 框架能干什么
能完成一定功能,加快應(yīng)用開(kāi)發(fā)進(jìn)度。
由于框架完成了一定的功能,而且通常是一些基礎(chǔ)的、有難度的、通用的功能,這就避免我們?cè)趹?yīng)用開(kāi)發(fā)的時(shí)候完全從頭開(kāi)始,而是在框架已有的功能之上繼續(xù)開(kāi)發(fā),也就是說(shuō)會(huì)復(fù)用框架的功能,從而加快應(yīng)用的開(kāi)發(fā)進(jìn)度。
給我們一個(gè)精良的程序架構(gòu)。框架定義了應(yīng)用的整體結(jié)構(gòu),包括類和對(duì)象的分割,各部分的主要責(zé)任,類和對(duì)象怎么協(xié)作,以及控制流程等等。
現(xiàn)在Java界大多數(shù)流行的框架,大都出自大師手筆,設(shè)計(jì)都很精良?;谶@樣的框架來(lái)開(kāi)發(fā),一般會(huì)遵循框架已經(jīng)規(guī)劃好的結(jié)構(gòu)來(lái)進(jìn)行開(kāi)發(fā),從而讓我們開(kāi)發(fā)的應(yīng)用程序的結(jié)構(gòu)也相對(duì)變得精良了。
- 對(duì)框架的理解
基于框架來(lái)開(kāi)發(fā),事情還是那些事情,只是看誰(shuí)做的問(wèn)題。
對(duì)于應(yīng)用程序和框架的關(guān)系,可以用一個(gè)圖來(lái)簡(jiǎn)單描述一下,如圖所示:
如果沒(méi)有框架,那么客戶要求的所有功能都由開(kāi)發(fā)人員自己來(lái)開(kāi)發(fā),沒(méi)問(wèn)題,同樣可以實(shí)現(xiàn)用戶要求的功能,只是開(kāi)發(fā)人員的工作多點(diǎn)。
如果有了框架,框架本身完成了一定的功能,那么框架已有的功能,開(kāi)發(fā)人員就可以不做了,開(kāi)發(fā)人員只需要完成框架沒(méi)有的功能,最后同樣是完成客戶要求的所有功能,但是開(kāi)發(fā)人員的工作就減少了。
也就是說(shuō),基于框架來(lái)開(kāi)發(fā),軟件要完成的功能并沒(méi)有變化,還是客戶要求的所有功能,也就是“事情還是那些事情”的意思。但是有了框架過(guò)后,框架完成了一部分功能,然后開(kāi)發(fā)人員再完成一部分功能,最后由框架和開(kāi)發(fā)人員合起來(lái)完成了整個(gè)軟件的功能,也就是看這些功能“由誰(shuí)做”的問(wèn)題。
基于框架開(kāi)發(fā),可以不去做框架所做的事情,但是應(yīng)該明白框架在干什么,以及框架是如何實(shí)現(xiàn)相應(yīng)功能的。
事實(shí)上,在實(shí)際開(kāi)發(fā)中,應(yīng)用程序和框架的關(guān)系,通常都不會(huì)如上面講述的那樣,分得那么清楚,更為普遍的是相互交互的,也就是應(yīng)用程序做一部分工作,然后框架做一部分工作,然后應(yīng)用程序再做一部分工作,然后框架再做一部分工作,如此交錯(cuò),最后由應(yīng)用程序和框架組合起來(lái)完成用戶的功能要求。
也用個(gè)圖來(lái)說(shuō)明,如圖所示:
如果把這個(gè)由應(yīng)用程序和框架組合在一起構(gòu)成的矩形,當(dāng)作最后完成的軟件。試想一下,如果你不懂框架在干什么的話,相當(dāng)于框架對(duì)你來(lái)講是個(gè)黑盒,也就是相當(dāng)于在上面圖中,去掉框架的兩塊,會(huì)發(fā)現(xiàn)什么?沒(méi)錯(cuò),剩下的應(yīng)用程序是支離破碎的,是相互分隔開(kāi)來(lái)的。
這會(huì)導(dǎo)致一個(gè)非常致命的問(wèn)題,整個(gè)應(yīng)用是如何運(yùn)轉(zhuǎn)起來(lái)的,你是不清楚的,也就是說(shuō)對(duì)你而言,項(xiàng)目已經(jīng)失控了,從項(xiàng)目管理的角度來(lái)講,這是很危險(xiǎn)的。
因此,在基于框架開(kāi)發(fā)的時(shí)候,雖然我們可以不去做框架所做的事情,但是應(yīng)該搞明白框架在干什么,如果條件許可的話,還應(yīng)該搞清楚框架是如何實(shí)現(xiàn)相應(yīng)功能的,至少應(yīng)該把大致的實(shí)現(xiàn)思路和實(shí)現(xiàn)步驟搞清楚,這樣我們才能整體的掌控整個(gè)項(xiàng)目,才能盡量減少出現(xiàn)項(xiàng)目失控的情況。
- 框架和設(shè)計(jì)模式的關(guān)系
設(shè)計(jì)模式比框架更抽象。
框架已經(jīng)是實(shí)現(xiàn)出來(lái)的軟件了,雖然只是個(gè)半成品的軟件,但畢竟是已經(jīng)實(shí)現(xiàn)出來(lái)的了。而設(shè)計(jì)模式的重心還在于解決問(wèn)題的方案上,也就是還停留在思想的層面。因此設(shè)計(jì)模式比框架更為抽象。
設(shè)計(jì)模式是比框架更小的體系結(jié)構(gòu)元素。
如上所述,框架是已經(jīng)實(shí)現(xiàn)出來(lái)的軟件,并實(shí)現(xiàn)了一系列的功能,因此一個(gè)框架,通常會(huì)包含多個(gè)設(shè)計(jì)模式的應(yīng)用。
框架比設(shè)計(jì)模式更加特例化。
框架是完成一定功能的半成品軟件,也就是說(shuō),框架的目的很明確,就是要解決某一個(gè)領(lǐng)域的某些問(wèn)題,那是很具體的功能,不同的領(lǐng)域?qū)崿F(xiàn)出來(lái)的框架是不一樣的。
而設(shè)計(jì)模式還停留在思想的層面,在不同的領(lǐng)域都可以應(yīng)用,只要相應(yīng)的問(wèn)題適合用某個(gè)設(shè)計(jì)模式來(lái)解決。因此框架總是針對(duì)特定領(lǐng)域的,而設(shè)計(jì)模式更加注重從思想上,從方法上來(lái)解決問(wèn)題,更加通用化。
1.3 有何問(wèn)題##
分析上面要實(shí)現(xiàn)的應(yīng)用框架,不管用戶選擇什么樣的導(dǎo)出格式,最后導(dǎo)出的都是一個(gè)文件,而且系統(tǒng)并不知道究竟要導(dǎo)出成為什么樣的文件,因此應(yīng)該有一個(gè)統(tǒng)一的接口,來(lái)描述系統(tǒng)最后生成的對(duì)象,并操作輸出的文件。
先把導(dǎo)出的文件對(duì)象的接口定義出來(lái),示例代碼如下:
/**
* 導(dǎo)出的文件對(duì)象的接口
*/
public interface ExportFileApi {
/**
* 導(dǎo)出內(nèi)容成為文件
* @param data 示意:需要保存的數(shù)據(jù)
* @return 是否導(dǎo)出成功
*/
public boolean export(String data);
}
對(duì)于實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象,它應(yīng)該根據(jù)需要來(lái)創(chuàng)建相應(yīng)的ExportFileApi的實(shí)現(xiàn)對(duì)象,因?yàn)樘囟ǖ腅xportFileApi的實(shí)現(xiàn)是與具體的業(yè)務(wù)相關(guān)的。但是對(duì)于實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象而言,它并不知道應(yīng)該創(chuàng)建哪一個(gè)ExportFileApi的實(shí)現(xiàn)對(duì)象,也不知道如何創(chuàng)建。
也就是說(shuō):對(duì)于實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象,它需要?jiǎng)?chuàng)建ExportFileApi的具體實(shí)例對(duì)象,但是它只知道ExportFileApi接口,而不知道其具體的實(shí)現(xiàn)。那該怎么辦呢?
2 解決方案#
2.1 工廠方法模式來(lái)解決##
用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案就是工廠方法模式。那么什么是工廠方法模式呢?
- 工廠方法模式定義
定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類決定實(shí)例化哪一個(gè)類,F(xiàn)actory Method使一個(gè)類的實(shí)例化延遲到其子類。
- 應(yīng)用工廠方法模式來(lái)解決的思路
仔細(xì)分析上面的問(wèn)題,事實(shí)上在實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象里面,根本就不知道究竟要使用哪一種導(dǎo)出文件的格式,因此這個(gè)對(duì)象本就不應(yīng)該和具體的導(dǎo)出文件的對(duì)象耦合在一起,它只需要面向?qū)С龅奈募?duì)象的接口就好了。
但是這樣一來(lái),又有新的問(wèn)題產(chǎn)生了:接口是不能直接使用的,需要使用具體的接口實(shí)現(xiàn)對(duì)象的實(shí)例。
這不是自相矛盾嗎?要求面向接口,不讓和具體的實(shí)現(xiàn)耦合,但是又需要?jiǎng)?chuàng)建接口的具體實(shí)現(xiàn)對(duì)象的實(shí)例。怎么解決這個(gè)矛盾呢?
工廠方法模式的解決思路很有意思,那就是不解決,采取無(wú)為而治的方式:不是需要接口對(duì)象嗎,那就定義一個(gè)方法來(lái)創(chuàng)建;可是事實(shí)上它自己是不知道如何創(chuàng)建這個(gè)接口對(duì)象的,沒(méi)有關(guān)系,那就定義成抽象方法就好了,自己實(shí)現(xiàn)不了,那就讓子類來(lái)實(shí)現(xiàn),這樣這個(gè)對(duì)象本身就可以只是面向接口編程,而無(wú)需關(guān)心到底如何創(chuàng)建接口對(duì)象了。
2.2 模式結(jié)構(gòu)和說(shuō)明##
工廠方法模式的結(jié)構(gòu)如圖所示:
Product:定義工廠方法所創(chuàng)建的對(duì)象的接口,也就是實(shí)際需要使用的對(duì)象的接口。
ConcreteProduct:具體的Product接口的實(shí)現(xiàn)對(duì)象。
Creator:創(chuàng)建器,聲明工廠方法,工廠方法通常會(huì)返回一個(gè)Product類型的實(shí)例對(duì)象,而且多是抽象方法。也可以在Creator里面提供工廠方法的默認(rèn)實(shí)現(xiàn),讓工廠方法返回一個(gè)缺省的Product類型的實(shí)例對(duì)象。
ConcreteCreator:具體的創(chuàng)建器對(duì)象,覆蓋實(shí)現(xiàn)Creator定義的工廠方法,返回具體的Product實(shí)例。
2.3 工廠方法模式示例代碼##
- 先看看Product的定義,示例代碼如下:
/**
* 工廠方法所創(chuàng)建的對(duì)象的接口
*/
public interface Product {
//可以定義Product的屬性和方法
}
- 再看看具體的Product的實(shí)現(xiàn)對(duì)象,示例代碼如下:
/**
* 具體的Product對(duì)象
*/
public class ConcreteProduct implements Product {
// 實(shí)現(xiàn)Product要求的方法
}
- 接下來(lái)看看創(chuàng)建器的定義,示例代碼如下:
/**
* 創(chuàng)建器,聲明工廠方法
*/
public abstract class Creator {
/**
* 創(chuàng)建Product的工廠方法
* @return Product對(duì)象
*/
protected abstract Product factoryMethod();
/**
* 示意方法,實(shí)現(xiàn)某些功能的方法
*/
public void someOperation() {
//通常在這些方法實(shí)現(xiàn)中,需要調(diào)用工廠方法來(lái)獲取Product對(duì)象
Product product = factoryMethod();
}
}
- 再看看具體的創(chuàng)建器實(shí)現(xiàn)對(duì)象,示例代碼如下:
/**
* 具體的創(chuàng)建器實(shí)現(xiàn)對(duì)象
*/
public class ConcreteCreator extends Creator {
protected Product factoryMethod() {
//重定義工廠方法,返回一個(gè)具體的Product對(duì)象
return new ConcreteProduct();
}
}
2.4 使用工廠方法模式來(lái)實(shí)現(xiàn)示例##
要使用工廠方法模式來(lái)實(shí)現(xiàn)示例,先來(lái)按照工廠方法模式的結(jié)構(gòu),對(duì)應(yīng)出哪些是被創(chuàng)建的Product,哪些是Creator。分析要求實(shí)現(xiàn)的功能,導(dǎo)出的文件對(duì)象接口ExportFileApi就相當(dāng)于是Product,而用來(lái)實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象就相當(dāng)于Creator。把Product和Creator分開(kāi)過(guò)后,就可以分別來(lái)實(shí)現(xiàn)它們了。
使用工廠方法模式來(lái)實(shí)現(xiàn)示例的程序結(jié)構(gòu)如圖所示:
導(dǎo)出的文件對(duì)象接口ExportFileApi的實(shí)現(xiàn)沒(méi)有變化,這里就不去贅述了
接下來(lái)看看接口ExportFileApi的實(shí)現(xiàn),為了示例簡(jiǎn)單,只實(shí)現(xiàn)導(dǎo)出文本文件格式和數(shù)據(jù)庫(kù)備份文件兩種。先看看導(dǎo)出文本文件格式的實(shí)現(xiàn),示例代碼如下:
/**
* 導(dǎo)出成文本文件格式的對(duì)象
*/
public class ExportTxtFile implements ExportFileApi{
public boolean export(String data) {
//簡(jiǎn)單示意一下,這里需要操作文件
System.out.println("導(dǎo)出數(shù)據(jù)"+data+"到文本文件");
return true;
}
}
/**
* 導(dǎo)出成數(shù)據(jù)庫(kù)備份文件形式的對(duì)象
*/
public class ExportDB implements ExportFileApi{
public boolean export(String data) {
//簡(jiǎn)單示意一下,這里需要操作數(shù)據(jù)庫(kù)和文件
System.out.println("導(dǎo)出數(shù)據(jù)"+data+"到數(shù)據(jù)庫(kù)備份文件");
return true;
}
}
- Creator這邊的實(shí)現(xiàn),首先看看ExportOperate的實(shí)現(xiàn),示例代碼如下:
/**
* 實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象
*/
public abstract class ExportOperate {
/**
* 導(dǎo)出文件
* @param data 需要保存的數(shù)據(jù)
* @return 是否成功導(dǎo)出文件
*/
public boolean export(String data){
//使用工廠方法
ExportFileApi api = factoryMethod();
return api.export(data);
}
/**
* 工廠方法,創(chuàng)建導(dǎo)出的文件對(duì)象的接口對(duì)象
* @return 導(dǎo)出的文件對(duì)象的接口對(duì)象
*/
protected abstract ExportFileApi factoryMethod();
}
- 加入了兩個(gè)Creator實(shí)現(xiàn),示例代碼如下:
/**
* 具體的創(chuàng)建器實(shí)現(xiàn)對(duì)象,實(shí)現(xiàn)創(chuàng)建導(dǎo)出成文本文件格式的對(duì)象
*/
public class ExportTxtFileOperate extends ExportOperate{
protected ExportFileApi factoryMethod() {
//創(chuàng)建導(dǎo)出成文本文件格式的對(duì)象
return new ExportTxtFile();
}
}
/**
* 具體的創(chuàng)建器實(shí)現(xiàn)對(duì)象,實(shí)現(xiàn)創(chuàng)建導(dǎo)出成數(shù)據(jù)庫(kù)備份文件形式的對(duì)象
*/
public class ExportDBOperate extends ExportOperate{
protected ExportFileApi factoryMethod() {
//創(chuàng)建導(dǎo)出成數(shù)據(jù)庫(kù)備份文件形式的對(duì)象
return new ExportDB();
}
}
- 客戶端直接創(chuàng)建需要使用的Creator對(duì)象,然后調(diào)用相應(yīng)的功能方法,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建需要使用的Creator對(duì)象
ExportOperate operate = new ExportDBOperate();
//調(diào)用輸出數(shù)據(jù)的功能方法
operate.export("測(cè)試數(shù)據(jù)");
}
}
3 模式講解#
3.1 認(rèn)識(shí)工廠方法模式##
- 模式的功能
工廠方法的主要功能是讓父類在不知道具體實(shí)現(xiàn)的情況下,完成自身的功能調(diào)用,而具體的實(shí)現(xiàn)延遲到子類來(lái)實(shí)現(xiàn)。
這樣在設(shè)計(jì)的時(shí)候,不用去考慮具體的實(shí)現(xiàn),需要某個(gè)對(duì)象,把它通過(guò)工廠方法返回就好了,在使用這些對(duì)象實(shí)現(xiàn)功能的時(shí)候還是通過(guò)接口來(lái)操作,這非常類似于IoC/DI的思想,這個(gè)在后面給大家稍詳細(xì)點(diǎn)介紹一下。
- 實(shí)現(xiàn)成抽象類
工廠方法的實(shí)現(xiàn)中,通常父類會(huì)是一個(gè)抽象類,里面包含創(chuàng)建所需對(duì)象的抽象方法,這些抽象方法就是工廠方法。
這里要注意一個(gè)問(wèn)題,子類在實(shí)現(xiàn)這些抽象方法的時(shí)候,通常并不是真的由子類來(lái)實(shí)現(xiàn)具體的功能,而是在子類的方法里面做選擇,選擇具體的產(chǎn)品實(shí)現(xiàn)對(duì)象。
父類里面,通常會(huì)有使用這些產(chǎn)品對(duì)象來(lái)實(shí)現(xiàn)一定的功能的方法,而且這些方法所實(shí)現(xiàn)的功能通常都是公共的功能,不管子類選擇了何種具體的產(chǎn)品實(shí)現(xiàn),這些方法的功能總是能正確執(zhí)行。
- 實(shí)現(xiàn)成具體的類
當(dāng)然也可以把父類實(shí)現(xiàn)成為一個(gè)具體的類,這種情況下,通常是在父類中提供獲取所需對(duì)象的默認(rèn)實(shí)現(xiàn)方法,這樣就算沒(méi)有具體的子類,也能夠運(yùn)行。
通常這種情況還是需要具體的子類來(lái)決定具體要如何創(chuàng)建父類所需要的對(duì)象。也把這種情況稱為工廠方法為子類提供了掛鉤,通過(guò)工廠方法,可以讓子類對(duì)象來(lái)覆蓋父類的實(shí)現(xiàn),從而提供更好的靈活性。
- 工廠方法的參數(shù)和返回
工廠方法的實(shí)現(xiàn)中,可能需要參數(shù),以便決定到底選用哪一種具體的實(shí)現(xiàn)。也就是說(shuō)通過(guò)在抽象方法里面?zhèn)鬟f參數(shù),在子類實(shí)現(xiàn)的時(shí)候根據(jù)參數(shù)進(jìn)行選擇,看看究竟應(yīng)該創(chuàng)建哪一個(gè)具體的實(shí)現(xiàn)對(duì)象。
一般工廠方法返回的是被創(chuàng)建對(duì)象的接口對(duì)象,當(dāng)然也可以是抽象類或者一個(gè)具體的類的實(shí)例。
- 誰(shuí)來(lái)使用工廠方法創(chuàng)建的對(duì)象
這里首先要搞明白一件事情,就是誰(shuí)在使用工廠方法創(chuàng)建的對(duì)象?
事實(shí)上,在工廠方法模式里面,應(yīng)該是Creator中的其它方法在使用工廠方法創(chuàng)建的對(duì)象,雖然也可以把工廠方法創(chuàng)建的對(duì)象直接提供給Creator外部使用,但工廠方法模式的本意,是由Creator對(duì)象內(nèi)部的方法來(lái)使用工廠方法創(chuàng)建的對(duì)象,也就是說(shuō),工廠方法一般不提供給Creator外部使用。
客戶端應(yīng)該是使用Creator對(duì)象,或者是使用由Creator創(chuàng)建出來(lái)的對(duì)象。對(duì)于客戶端使用Creator對(duì)象,這個(gè)時(shí)候工廠方法創(chuàng)建的對(duì)象,是Creator中的某些方法使用。對(duì)于使用那些由Creator創(chuàng)建出來(lái)的對(duì)象,這個(gè)時(shí)候工廠方法創(chuàng)建的對(duì)象,是構(gòu)成客戶端需要的對(duì)象的一部分。分別舉例來(lái)說(shuō)明。
① 客戶端使用Creator對(duì)象的情況
比如前面的示例,對(duì)于“實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象”的類ExportOperate,它有一個(gè)export的方法,在這個(gè)方法里面,需要使用具體的“導(dǎo)出的文件對(duì)象的接口對(duì)象” ExportFileApi,而ExportOperate是不知道具體的ExportFileApi實(shí)現(xiàn)的,那么怎么做的呢?就是定義了一個(gè)工廠方法,用來(lái)返回ExportFileApi的對(duì)象,然后export方法會(huì)使用這個(gè)工廠方法來(lái)獲取它所需要的對(duì)象,然后執(zhí)行功能。
這個(gè)時(shí)候的客戶端是怎么做的呢?這個(gè)時(shí)候客戶端主要就是使用這個(gè)ExportOperate的實(shí)例來(lái)完成它想要完成的功能,也就是客戶端使用Creator對(duì)象的情況,簡(jiǎn)單描述這種情況下的代碼結(jié)構(gòu)如下:
/**
* 客戶端使用Creator對(duì)象的情況下,Creator的基本實(shí)現(xiàn)結(jié)構(gòu)
*/
public abstract class Creator {
/**
* 工廠方法,一般不對(duì)外
* @return 創(chuàng)建的產(chǎn)品對(duì)象
*/
protected abstract Product factoryMethod();
/**
* 提供給外部使用的方法,
* 客戶端一般使用Creator提供的這些方法來(lái)完成所需要的功能
*/
public void someOperation(){
//在這里使用工廠方法
Product p = factoryMethod();
}
}
② 客戶端使用由Creator創(chuàng)建出來(lái)的對(duì)象
另外一種是由Creator向客戶端返回由“工廠方法創(chuàng)建的對(duì)象”來(lái)構(gòu)建的對(duì)象,這個(gè)時(shí)候工廠方法創(chuàng)建的對(duì)象,是構(gòu)成客戶端需要的對(duì)象的一部分。簡(jiǎn)單描述這種情況下的代碼結(jié)構(gòu)如下:
/**
* 客戶端使用Creator來(lái)創(chuàng)建客戶端需要的對(duì)象的情況下,Creator的基本實(shí)現(xiàn)結(jié)構(gòu)
*/
public abstract class Creator {
/**
* 工廠方法,一般不對(duì)外,創(chuàng)建一個(gè)部件對(duì)象
* @return 創(chuàng)建的產(chǎn)品對(duì)象,一般是另一個(gè)產(chǎn)品對(duì)象的部件
*/
protected abstract Product1 factoryMethod1();
/**
* 工廠方法,一般不對(duì)外,創(chuàng)建一個(gè)部件對(duì)象
* @return 創(chuàng)建的產(chǎn)品對(duì)象,一般是另一個(gè)產(chǎn)品對(duì)象的部件
*/
protected abstract Product2 factoryMethod2();
/**
* 創(chuàng)建客戶端需要的對(duì)象,客戶端主要使用產(chǎn)品對(duì)象來(lái)完成所需要的功能
* @return 客戶端需要的對(duì)象
*/
public Product createProduct(){
//在這里使用工廠方法,得到客戶端所需對(duì)象的部件對(duì)象
Product1 p1 = factoryMethod1();
Product2 p2 = factoryMethod2();
//工廠方法創(chuàng)建的對(duì)象是創(chuàng)建客戶端對(duì)象所需要的
Product p = new ConcreteProduct();
p.setProduct1(p1);
p.setProduct2(p2);
return p;
}
}
小結(jié)一下:在工廠方法模式里面,客戶端要么使用Creator對(duì)象,要么使用Creator創(chuàng)建的對(duì)象,一般客戶端不直接使用工廠方法。當(dāng)然也可以直接把工廠方法暴露給客戶端操作,但是一般不這么做。
- 工廠方法模式的調(diào)用順序示意圖
由于客戶端使用Creator對(duì)象有兩種典型的情況,因此調(diào)用的順序示意圖也分做兩種情況,先看看客戶端使用由Creator創(chuàng)建出來(lái)的對(duì)象情況的調(diào)用順序示意圖,如圖所示:
接下來(lái)看看客戶端使用Creator對(duì)象時(shí)候的調(diào)用順序示意圖,如圖所示:
3.2 工廠方法模式與IoC/DI##
IoC——Inversion of Control 控制反轉(zhuǎn)
DI——Dependency Injection 依賴注入
- 如何理解IoC/DI
要想理解上面兩個(gè)概念,就必須搞清楚如下的問(wèn)題:
參與者都有誰(shuí)?
依賴:誰(shuí)依賴于誰(shuí)?為什么需要依賴?
注入:誰(shuí)注入于誰(shuí)?到底注入什么?
控制反轉(zhuǎn):誰(shuí)控制誰(shuí)?控制什么?為何叫反轉(zhuǎn)(有反轉(zhuǎn)就應(yīng)該有正轉(zhuǎn)了)?
依賴注入和控制反轉(zhuǎn)是同一概念嗎?
(1) 參與者都有誰(shuí):
一般有三方參與者,一個(gè)是某個(gè)對(duì)象;一個(gè)是IoC/DI的容器;另一個(gè)是某個(gè)對(duì)象的外部資源。
又要名詞解釋一下,某個(gè)對(duì)象指的就是任意的、普通的Java對(duì)象; IoC/DI的容器簡(jiǎn)單點(diǎn)說(shuō)就是指用來(lái)實(shí)現(xiàn)IoC/DI功能的一個(gè)框架程序;對(duì)象的外部資源指的就是對(duì)象需要的,但是是從對(duì)象外部獲取的,都統(tǒng)稱資源,比如:對(duì)象需要的其它對(duì)象、或者是對(duì)象需要的文件資源等等。
(2) 誰(shuí)依賴于誰(shuí):當(dāng)然是某個(gè)對(duì)象依賴于IoC/DI的容器
(3) 為什么需要依賴:對(duì)象需要IoC/DI的容器來(lái)提供對(duì)象需要的外部資源
(4) 誰(shuí)注入于誰(shuí):很明顯是IoC/DI的容器 注入 某個(gè)對(duì)象
(5) 到底注入什么:就是注入某個(gè)對(duì)象所需要的外部資源
(6) 誰(shuí)控制誰(shuí):當(dāng)然是IoC/DI的容器來(lái)控制對(duì)象了
(7) 控制什么:主要是控制對(duì)象實(shí)例的創(chuàng)建
(8) 為何叫反轉(zhuǎn):
反轉(zhuǎn)是相對(duì)于正向而言的,那么什么算是正向的呢?考慮一下常規(guī)情況下的應(yīng)用程序,如果要在A里面使用C,你會(huì)怎么做呢?當(dāng)然是直接去創(chuàng)建C的對(duì)象,也就是說(shuō),是在A類中主動(dòng)去獲取所需要的外部資源C,這種情況被稱為正向的。那么什么是反向呢?就是A類不再主動(dòng)去獲取C,而是被動(dòng)等待,等待IoC/DI的容器獲取一個(gè)C的實(shí)例,然后反向的注入到A類中。
用圖例來(lái)說(shuō)明一下,先看沒(méi)有IoC/DI的時(shí)候,常規(guī)的A類使用C類的示意圖,如圖所示:
當(dāng)有了IoC/DI的容器后,A類不再主動(dòng)去創(chuàng)建C了,如圖所示:
而是被動(dòng)等待,等待IoC/DI的容器獲取一個(gè)C的實(shí)例,然后反向的注入到A類中,如圖所示:
(9) 依賴注入和控制反轉(zhuǎn)是同一概念嗎?
根據(jù)上面的講述,應(yīng)該能看出來(lái),依賴注入和控制反轉(zhuǎn)是對(duì)同一件事情的不同描述,從某個(gè)方面講,就是它們描述的角度不同。依賴注入是從應(yīng)用程序的角度在描述,可以把依賴注入描述完整點(diǎn):應(yīng)用程序依賴容器創(chuàng)建并注入它所需要的外部資源;而控制反轉(zhuǎn)是從容器的角度在描述,描述完整點(diǎn):容器控制應(yīng)用程序,由容器反向的向應(yīng)用程序注入應(yīng)用程序所需要的外部資源。
(10) 小結(jié)一下:
其實(shí)IoC/DI對(duì)編程帶來(lái)的最大改變不是從代碼上,而是從思想上,發(fā)生了“主從換位”的變化。應(yīng)用程序原本是老大,要獲取什么資源都是主動(dòng)出擊,但是在IoC/DI思想中,應(yīng)用程序就變成被動(dòng)的了,被動(dòng)的等待IoC/DI容器來(lái)創(chuàng)建并注入它所需要的資源了。
這么小小的一個(gè)改變其實(shí)是編程思想的一個(gè)大進(jìn)步,這樣就有效的分離了對(duì)象和它所需要的外部資源,使得它們松散耦合,有利于功能復(fù)用,更重要的是使得程序的整個(gè)體系結(jié)構(gòu)變得非常靈活。
- 工廠方法模式和IoC/DI有什么關(guān)系呢?
從某個(gè)角度講,它們的思想很類似。
上面講了,有了IoC/DI過(guò)后,應(yīng)用程序就不再主動(dòng)了,而是被動(dòng)等待由容器來(lái)注入資源,那么在編寫(xiě)代碼的時(shí)候,一旦要用到外部資源,就會(huì)開(kāi)一個(gè)窗口,讓容器能注入進(jìn)來(lái),也就是提供給容器使用的注入的途徑,當(dāng)然這不是我們的重點(diǎn),就不去細(xì)細(xì)講了,用setter注入來(lái)示例一下,看看使用IoC/DI的代碼是什么樣子,示例代碼如下:
public class A {
/**
* 等待被注入進(jìn)來(lái)
*/
private C c = null;
/**
* 注入資源C的方法
* @param c 被注入的資源
*/
public void setC(C c){
this.c = c;
}
public void t1(){
//這里需要使用C,可是又不讓主動(dòng)去創(chuàng)建C了,怎么辦?
//反正就要求從外部注入,這樣更省心,
//自己不用管怎么獲取C,直接使用就好了
c.tc();
}
}
接口C的示例代碼如下:
public interface C {
public void tc();
}
從上面的示例代碼可以看出,現(xiàn)在在A里面寫(xiě)代碼的時(shí)候,凡是碰到了需要外部資源,那么就提供注入的途徑,要求從外部注入,自己只管使用這些對(duì)象。
public abstract class A1 {
/**
* 工廠方法,創(chuàng)建C1,類似于從子類注入進(jìn)來(lái)的途徑
* @return C1的對(duì)象實(shí)例
*/
protected abstract C1 createC1();
public void t1(){
//這里需要使用C1類,可是不知道究竟是用哪一個(gè)
//也就不主動(dòng)去創(chuàng)建C1了,怎么辦?
//反正會(huì)在子類里面實(shí)現(xiàn),這里不用管怎么獲取C1,直接使用就好了
createC1().tc();
}
}
子類的示例代碼如下:
public class A2 extends A1 {
protected C1 createC1() {
//真正的選擇具體實(shí)現(xiàn),并創(chuàng)建對(duì)象
return new C2();
}
}
C1接口和前面C接口是一樣的,C2這個(gè)實(shí)現(xiàn)類也是空的,只是演示一下,因此就不去展示它們的代碼了。
仔細(xì)體會(huì)上面的示例,對(duì)比它們的實(shí)現(xiàn),尤其是從思想層面上,會(huì)發(fā)現(xiàn)工廠方法模式和IoC/DI的思想是相似的,都是“主動(dòng)變被動(dòng)”,進(jìn)行了“主從換位”,從而獲得了更靈活的程序結(jié)構(gòu)。
3.3 平行的類層次結(jié)構(gòu)##
- 什么是平行的類層次結(jié)構(gòu)呢?
簡(jiǎn)單點(diǎn)說(shuō),假如有兩個(gè)類層次結(jié)構(gòu),其中一個(gè)類層次中的每個(gè)類在另一個(gè)類層次中都有一個(gè)對(duì)應(yīng)的類的結(jié)構(gòu),就被稱為平行的類層次結(jié)構(gòu)。
舉個(gè)例子來(lái)說(shuō),硬盤(pán)對(duì)象有很多種,如分成臺(tái)式機(jī)硬盤(pán)和筆記本硬盤(pán),在臺(tái)式機(jī)硬盤(pán)的具體實(shí)現(xiàn)上面,又有希捷、西數(shù)等不同品牌的實(shí)現(xiàn),同樣在筆記本硬盤(pán)上,也有希捷、日立、IBM等不同品牌的實(shí)現(xiàn);硬盤(pán)對(duì)象具有自己的行為,如硬盤(pán)能存儲(chǔ)數(shù)據(jù),也能從硬盤(pán)上獲取數(shù)據(jù),不同的硬盤(pán)對(duì)象對(duì)應(yīng)的行為對(duì)象是不一樣的,因?yàn)椴煌挠脖P(pán)對(duì)象,它的行為的實(shí)現(xiàn)方式是不一樣的。如果把硬盤(pán)對(duì)象和硬盤(pán)對(duì)象的行為分開(kāi)描述,那么就構(gòu)成了如圖所示的結(jié)構(gòu):
硬盤(pán)對(duì)象是一個(gè)類層次,硬盤(pán)的行為這邊也是一個(gè)類層次,而且兩個(gè)類層次中的類是對(duì)應(yīng)的。臺(tái)式機(jī)西捷硬盤(pán)對(duì)象就對(duì)應(yīng)著硬盤(pán)行為里面的臺(tái)式機(jī)西捷硬盤(pán)的行為;筆記本IBM硬盤(pán)就對(duì)應(yīng)著筆記本IBM硬盤(pán)的行為,這就是一種典型的平行的類層次結(jié)構(gòu)。
這種平行的類層次結(jié)構(gòu)用來(lái)干什么呢?主要用來(lái)把一個(gè)類層次中的某些行為分離出來(lái),讓類層次中的類把原本屬于自己的職責(zé),委托給分離出來(lái)的類去實(shí)現(xiàn),從而使得類層次本身變得更簡(jiǎn)單,更容易擴(kuò)展和復(fù)用。
一般來(lái)講,分離出去的這些類的行為,會(huì)對(duì)應(yīng)著類層次結(jié)構(gòu)來(lái)組織,從而形成一個(gè)新的類層次結(jié)構(gòu),相當(dāng)于原來(lái)對(duì)象的行為的這么一個(gè)類層次結(jié)構(gòu),而這個(gè)層次結(jié)構(gòu)和原來(lái)的類層次結(jié)構(gòu)是存在對(duì)應(yīng)關(guān)系的,因此被稱為平行的類層次結(jié)構(gòu)。
- 工廠方法模式跟平行的類層次結(jié)構(gòu)有何關(guān)系呢?
可以使用工廠方法模式來(lái)連接平行的類層次。
看上面的示例圖,在每個(gè)硬盤(pán)對(duì)象里面,都有一個(gè)工廠方法createHDOperate,通過(guò)這個(gè)工廠方法,客戶端就可以獲取一個(gè)跟硬盤(pán)對(duì)象相對(duì)應(yīng)的行為對(duì)象。在硬盤(pán)對(duì)象的子類里面,會(huì)覆蓋父類的工廠方法createHDOperate,以提供跟自身相對(duì)應(yīng)的行為對(duì)象,從而自然的把兩個(gè)平行的類層次連接起來(lái)使用。
3.4 參數(shù)化工廠方法##
所謂參數(shù)化工廠方法指的就是:通過(guò)給工廠方法傳遞參數(shù),讓工廠方法根據(jù)參數(shù)的不同來(lái)創(chuàng)建不同的產(chǎn)品對(duì)象,這種情況就被稱為參數(shù)化工廠方法。當(dāng)然工廠方法創(chuàng)建的不同的產(chǎn)品必須是同一個(gè)Product類型的。
來(lái)改造前面的示例,現(xiàn)在有一個(gè)工廠方法來(lái)創(chuàng)建ExportFileApi這個(gè)產(chǎn)品的對(duì)象,但是ExportFileApi接口的具體實(shí)現(xiàn)很多,為了方便創(chuàng)建的選擇,直接從客戶端傳入一個(gè)參數(shù),這樣在需要?jiǎng)?chuàng)建ExportFileApi對(duì)象的時(shí)候,就把這個(gè)參數(shù)傳遞給工廠方法,讓工廠方法來(lái)實(shí)例化具體的ExportFileApi實(shí)現(xiàn)對(duì)象。
- 先來(lái)看Product的接口,就是ExportFileApi接口,跟前面的示例沒(méi)有任何變化,為了方便大家查看,這里重復(fù)一下,示例代碼如下:
/**
* 導(dǎo)出的文件對(duì)象的接口
*/
public interface ExportFileApi {
/**
* 導(dǎo)出內(nèi)容成為文件
* @param data 示意:需要保存的數(shù)據(jù)
* @return 是否導(dǎo)出成功
*/
public boolean export(String data);
}
- 同樣提供保存成文本文件和保存成數(shù)據(jù)庫(kù)備份文件的實(shí)現(xiàn),跟前面的示例沒(méi)有任何變化,示例代碼如下:
public class ExportTxtFile implements ExportFileApi{
public boolean export(String data) {
//簡(jiǎn)單示意一下,這里需要操作文件
System.out.println("導(dǎo)出數(shù)據(jù)"+data+"到文本文件");
return true;
}
}
public class ExportDB implements ExportFileApi{
public boolean export(String data) {
//簡(jiǎn)單示意一下,這里需要操作數(shù)據(jù)庫(kù)和文件
System.out.println("導(dǎo)出數(shù)據(jù)"+data+"到數(shù)據(jù)庫(kù)備份文件");
return true;
}
}
- 接下來(lái)該看看ExportOperate類了,這個(gè)類的變化大致如下:
ExportOperate類中的創(chuàng)建產(chǎn)品的工廠方法,通常需要提供默認(rèn)的實(shí)現(xiàn),不抽象了,也就是變成正常方法。
ExportOperate類也不再定義成抽象類了,因?yàn)橛辛四J(rèn)的實(shí)現(xiàn),客戶端可能需要直接使用這個(gè)對(duì)象。
設(shè)置一個(gè)導(dǎo)出類型的參數(shù),通過(guò)export方法從客戶端傳入。
/**
* 實(shí)現(xiàn)導(dǎo)出數(shù)據(jù)的業(yè)務(wù)功能對(duì)象
*/
public class ExportOperate {
/**
* 導(dǎo)出文件
* @param type 用戶選擇的導(dǎo)出類型
* @param data 需要保存的數(shù)據(jù)
* @return 是否成功導(dǎo)出文件
*/
public boolean export(int type,String data){
//使用工廠方法
ExportFileApi api = factoryMethod(type);
return api.export(data);
}
/**
* 工廠方法,創(chuàng)建導(dǎo)出的文件對(duì)象的接口對(duì)象
* @param type 用戶選擇的導(dǎo)出類型
* @return 導(dǎo)出的文件對(duì)象的接口對(duì)象
*/
protected ExportFileApi factoryMethod(int type){
ExportFileApi api = null;
//根據(jù)類型來(lái)選擇究竟要?jiǎng)?chuàng)建哪一種導(dǎo)出文件對(duì)象
if(type==1){
api = new ExportTxtFile();
}else if(type==2){
api = new ExportDB();
}
return api;
}
}
- 此時(shí)的客戶端,非常簡(jiǎn)單,直接使用ExportOperate類,示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建需要使用的Creator對(duì)象
ExportOperate operate = new ExportOperate();
//調(diào)用輸出數(shù)據(jù)的功能方法,傳入選擇到處類型的參數(shù)
operate.export(1,"測(cè)試數(shù)據(jù)");
}
}
測(cè)試看看,然后修改一下客戶端的參數(shù),體會(huì)一下通過(guò)參數(shù)來(lái)選擇具體的導(dǎo)出實(shí)現(xiàn)的過(guò)程。這是一種很常見(jiàn)的參數(shù)化工廠方法的實(shí)現(xiàn)方式,但是也還是有把參數(shù)化工廠方法實(shí)現(xiàn)成為抽象的,這點(diǎn)要注意,并不是說(shuō)參數(shù)化工廠方法就不能實(shí)現(xiàn)成為抽象類了。只是一般情況下,參數(shù)化工廠方法,在父類都會(huì)提供默認(rèn)的實(shí)現(xiàn)。
- 擴(kuò)展新的實(shí)現(xiàn)
使用參數(shù)化工廠方法,擴(kuò)展起來(lái)會(huì)非常容易,已有的代碼都不會(huì)改變,只要新加入一個(gè)子類來(lái)提供新的工廠方法實(shí)現(xiàn),然后在客戶端使用這個(gè)新的子類即可。
這種實(shí)現(xiàn)方式還有一個(gè)有意思的功能,就是子類可以選擇性覆蓋,不想覆蓋的功能還可以返回去讓父類來(lái)實(shí)現(xiàn),很有意思。
先擴(kuò)展一個(gè)導(dǎo)出成xml文件的實(shí)現(xiàn),試試看,示例代碼如下:
/**
* 導(dǎo)出成xml文件的對(duì)象
*/
public class ExportXml implements ExportFileApi{
public boolean export(String data) {
//簡(jiǎn)單示意一下
System.out.println("導(dǎo)出數(shù)據(jù)"+data+"到XML文件");
return true;
}
}
然后擴(kuò)展ExportOperate類,來(lái)加入新的實(shí)現(xiàn),示例代碼如下:
/**
* 擴(kuò)展ExportOperate對(duì)象,加入可以導(dǎo)出XML文件
*/
public class ExportOperate2 extends ExportOperate{
/**
* 覆蓋父類的工廠方法,創(chuàng)建導(dǎo)出的文件對(duì)象的接口對(duì)象
* @param type 用戶選擇的導(dǎo)出類型
* @return 導(dǎo)出的文件對(duì)象的接口對(duì)象
*/
protected ExportFileApi factoryMethod(int type){
ExportFileApi api = null;
//可以全部覆蓋,也可以選擇自己感興趣的覆蓋,
//這里只想添加自己新的實(shí)現(xiàn),其它的不管
if(type==3){
api = new ExportXml();
}else{
//其它的還是讓父類來(lái)實(shí)現(xiàn)
api = super.factoryMethod(type);
}
return api;
}
}
看看此時(shí)的客戶端,也非常簡(jiǎn)單,只是在變換傳入的參數(shù),示例代碼如下:
public class Client {
public static void main(String[] args) {
//創(chuàng)建需要使用的Creator對(duì)象
ExportOperate operate = new ExportOperate2();
//下面變換傳入的參數(shù)來(lái)測(cè)試參數(shù)化工廠方法
operate.export(1,"Test1");
operate.export(2,"Test2");
operate.export(3,"Test3");
}
}
3.5 工廠方法模式的優(yōu)缺點(diǎn)##
- 可以在不知具體實(shí)現(xiàn)的情況下編程
工廠方法模式可以讓你在實(shí)現(xiàn)功能的時(shí)候,如果需要某個(gè)產(chǎn)品對(duì)象,只需要使用產(chǎn)品的接口即可,而無(wú)需關(guān)心具體的實(shí)現(xiàn)。選擇具體實(shí)現(xiàn)的任務(wù)延遲到子類去完成。
更容易擴(kuò)展對(duì)象的新版本。
工廠方法給子類提供了一個(gè)掛鉤,使得擴(kuò)展新的對(duì)象版本變得非常容易。比如上面示例的參數(shù)化工廠方法實(shí)現(xiàn)中,擴(kuò)展一個(gè)新的導(dǎo)出Xml文件格式的實(shí)現(xiàn),已有的代碼都不會(huì)改變,只要新加入一個(gè)子類來(lái)提供新的工廠方法實(shí)現(xiàn),然后在客戶端使用這個(gè)新的子類即可。
另外這里提到的掛鉤,就是我們經(jīng)常說(shuō)的鉤子方法(hook),這個(gè)會(huì)在后面講模板方法模式的時(shí)候詳細(xì)點(diǎn)說(shuō)明。
- 連接平行的類層次
工廠方法除了創(chuàng)造產(chǎn)品對(duì)象外,在連接平行的類層次上也大顯身手。這個(gè)在前面已經(jīng)詳細(xì)講述了。
- 具體產(chǎn)品對(duì)象和工廠方法的耦合性
在工廠方法模式里面,工廠方法是需要?jiǎng)?chuàng)建產(chǎn)品對(duì)象的,也就是需要選擇具體的產(chǎn)品對(duì)象,并創(chuàng)建它們的實(shí)例,因此具體產(chǎn)品對(duì)象和工廠方法是耦合的。
3.6 思考工廠方法模式##
- 工廠方法模式的本質(zhì)
工廠方法模式的本質(zhì):延遲到子類來(lái)選擇實(shí)現(xiàn)。
仔細(xì)體會(huì)前面的示例,你會(huì)發(fā)現(xiàn),工廠方法模式中的工廠方法,在真正實(shí)現(xiàn)的時(shí)候,一般是先選擇具體使用哪一個(gè)具體的產(chǎn)品實(shí)現(xiàn)對(duì)象,然后創(chuàng)建這個(gè)具體產(chǎn)品對(duì)象的示例,然后就可以返回去了。也就是說(shuō),工廠方法本身并不會(huì)去實(shí)現(xiàn)產(chǎn)品接口,具體的產(chǎn)品實(shí)現(xiàn)是已經(jīng)寫(xiě)好了的,工廠方法只要去選擇實(shí)現(xiàn)就好了。
有些朋友可能會(huì)說(shuō),這不是跟簡(jiǎn)單工廠一樣嗎?
確實(shí)從本質(zhì)上講,它們是非常類似的,具體實(shí)現(xiàn)上都是在“選擇實(shí)現(xiàn)”。但是也存在不同點(diǎn),簡(jiǎn)單工廠是直接在工廠類里面進(jìn)行“選擇實(shí)現(xiàn)”;而工廠方法會(huì)把這個(gè)工作延遲到子類來(lái)實(shí)現(xiàn),工廠類里面使用工廠方法的地方是依賴于抽象而不是具體的實(shí)現(xiàn),從而使得系統(tǒng)更加靈活,具有更好的可維護(hù)性和可擴(kuò)展性。
其實(shí)如果把工廠方法模式中的Creator退化一下,只提供工廠方法,而且這些工廠方法還都提供默認(rèn)的實(shí)現(xiàn),那不就變成了簡(jiǎn)單工廠了嗎?比如把剛才示范參數(shù)化工廠方法的例子代碼拿過(guò)來(lái)再簡(jiǎn)化一下,你就能看出來(lái),寫(xiě)得跟簡(jiǎn)單工廠是差不多的,示例代碼如下:
看完上述代碼,會(huì)體會(huì)到簡(jiǎn)單工廠和工廠方法模式是有很大相似性的了吧,從某個(gè)角度來(lái)講,可以認(rèn)為簡(jiǎn)單工廠就是工廠方法模式的一種特例,因此它們的本質(zhì)是類似的,也就不足為奇了。
- 對(duì)設(shè)計(jì)原則的體現(xiàn)
工廠方法模式很好的體現(xiàn)了“依賴倒置原則”。
依賴倒置原則告訴我們“要依賴抽象,不要依賴于具體類”,簡(jiǎn)單點(diǎn)說(shuō)就是:不能讓高層組件依賴于低層組件,而且不管高層組件還是低層組件,都應(yīng)該依賴于抽象。
比如前面的示例,實(shí)現(xiàn)客戶端請(qǐng)求操作的ExportOperate就是高層組件;而具體實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出的對(duì)象就是低層組件,比如ExportTxtFile、ExportDB;而ExportFileApi接口就相當(dāng)于是那個(gè)抽象。
對(duì)于ExportOperate來(lái)說(shuō),它不關(guān)心具體的實(shí)現(xiàn)方式,它只是“面向接口編程”;對(duì)于具體的實(shí)現(xiàn)來(lái)說(shuō),它只關(guān)心自己“如何實(shí)現(xiàn)接口”所要求的功能。
那么倒置的是什么呢?倒置的是這個(gè)接口的“所有權(quán)”。事實(shí)上,ExportFileApi接口中定義的功能,都是由高層組件ExportOperate來(lái)提出的要求,也就是說(shuō)接口中的功能,是高層組件需要的功能。但是高層組件只是提出要求,并不關(guān)心如何實(shí)現(xiàn),而低層組件,就是來(lái)真正實(shí)現(xiàn)高層組件所要求的接口功能的。因此看起來(lái),低層實(shí)現(xiàn)的接口的所有權(quán)并不在底層組件手中,而是倒置到高層組件去了。
- 何時(shí)選用工廠方法模式
建議在如下情況中,選用工廠方法模式:
如果一個(gè)類需要?jiǎng)?chuàng)建某個(gè)接口的對(duì)象,但是又不知道具體的實(shí)現(xiàn),這種情況可以選用工廠方法模式,把創(chuàng)建對(duì)象的工作延遲到子類去實(shí)現(xiàn)。
如果一個(gè)類本身就希望,由它的子類來(lái)創(chuàng)建所需的對(duì)象的時(shí)候,應(yīng)該使用工廠方法模式。
3.7 相關(guān)模式##
- 工廠方法模式和抽象工廠模式
這兩個(gè)模式可以組合使用,具體的放到抽象工廠模式中去講。
- 工廠方法模式和模板方法模式
這兩個(gè)模式外觀類似,都是有一個(gè)抽象類,然后由子類來(lái)提供一些實(shí)現(xiàn),但是工廠方法模式的子類專注的是創(chuàng)建產(chǎn)品對(duì)象,而模板方法模式的子類專注的是為固定的算法骨架提供某些步驟的實(shí)現(xiàn)。
這兩個(gè)模式可以組合使用,通常在模板方法模式里面,使用工廠方法來(lái)創(chuàng)建模板方法需要的對(duì)象。