說起這個(gè)工廠模式,一時(shí)還真不知道該如何說起。反正這是我的開發(fā)日志,不提理論的東西,理論的東西那里都有,我只想把具體實(shí)踐記錄下來給師弟師妹們作個(gè)參考,積累點(diǎn)經(jīng)驗(yàn)。所有這些文字都是集中講一點(diǎn)――“在什么情況下為什么用某種模式好,為什么好,為什么在那種情況下能想起來用?”。
研究生院項(xiàng)目中“明顯”使用了“工廠方法模式”。其實(shí)在遇到具體問題的時(shí)候,即使我們不知道有這個(gè)模式存在,我們也肯定會(huì)造一個(gè)類似的東西出來。但是,肯定沒有書上論述的那么好,那么全面。我想這就是看書的好處吧。
工廠方法出現(xiàn)的必然(我的理解,一個(gè)很狹隘并幼稚理的人的理解)
剛開始使用這個(gè)東西的時(shí)候,只是感覺是單純的一種模式,用于創(chuàng)建需要的對(duì)象。
但是隨著使用和思考的深入,越發(fā)發(fā)現(xiàn)它給我的啟示不只在于單純的對(duì)象創(chuàng)建,而是告訴我應(yīng)該怎么理解“產(chǎn)品”,怎么得到“產(chǎn)品”,怎么消費(fèi)“產(chǎn)品”,以至于以后怎么設(shè)計(jì)“產(chǎn)品”。
下面這個(gè)線索是我對(duì)它出現(xiàn)必然性的理解:
1. “針對(duì)接口編程”
這是OO世界中經(jīng)典的規(guī)范,不管你主動(dòng)還是被動(dòng),你天天都在用這個(gè)東西。
接口是共性的表示,在對(duì)象的世界中,共性和個(gè)性的辯證關(guān)系是最重要的關(guān)系。在萬千的對(duì)象中,通過它們之間的共性和個(gè)性,可以形成最基本對(duì)象層級(jí)架構(gòu)。
假設(shè)我們的討論域中有以下一些對(duì)象:“學(xué)生”、“大學(xué)生”、“小學(xué)生”、“中學(xué)生”;我們不用細(xì)想,學(xué)過一天OO的人都可以為這些耳熟能詳?shù)膶?duì)象們,通過個(gè)性和共性的關(guān)系得出下面的結(jié)構(gòu)圖。

把這些對(duì)象之間的關(guān)系定義成這樣是順理成章的。
下一步肯定是讓客戶端“使用”這個(gè)接口啦。也就是如下圖:

2. 接口和具體類的矛盾
勿庸置疑,我們只希望Client跟接口Student打交道,讓它根本就不知道Student有哪些子類,絕對(duì)不希望直接跟它們打交道。
但這里出現(xiàn)的困難是,接口都是“假”的,都是由具體類upcast的。
如果Client要使用接口Student,Client中必須會(huì)出現(xiàn)下面的代碼:
Student marco = new Small_Student();
只要一出現(xiàn)這個(gè)代碼,就說明Client不只跟Student打交道了,它知道了Small_Student類,這違反了我們預(yù)先的想法。
3. 找“人”幫我去創(chuàng)建“接口對(duì)象”
從上圖體現(xiàn)出來的結(jié)構(gòu)看,Client只想跟Student打交道的目的是實(shí)現(xiàn)不了的了。
最簡單的方法就是找另外的“幫手”去幫我生成這個(gè)“接口對(duì)象”。這個(gè)幫手它知道“接口對(duì)象”的具體類型,但是它為客戶端提供的卻一定是“接口類型”。這就符合我們的要求了。如圖:

這樣,Client就可以既用到了“Student接口對(duì)象”,又不用因?yàn)椤爸挥芯唧w類才能創(chuàng)建對(duì)象”的規(guī)則,而必須對(duì)其子類結(jié)構(gòu)有完全的了解。它成功的解決了2中的矛盾。
而“負(fù)責(zé)創(chuàng)建具體類對(duì)象的任務(wù)”全部都落在了這個(gè)“幫手”身上,這個(gè)“幫手”(Student_Factory)就是工廠模式中的工廠類,更具體的說,它就是“簡單工廠模式”中的“簡單工廠類”。
我覺得,即使一點(diǎn)都不知道工廠模式,一旦我遇到了2里說的矛盾,我也會(huì)用這樣的方法處理。
4. 這個(gè)“幫手”不符合“開-閉原則”
這個(gè)幫手的確不錯(cuò)了,而且一躍成為系統(tǒng)中最重要的對(duì)象了,所有“創(chuàng)建具體類的邏輯”都放進(jìn)去了,也就是因?yàn)橹匾f一掛了不就慘了。
再者,它不符合“開-閉”原則,我不能在不修改這個(gè)幫手的情況下添加任何一個(gè)產(chǎn)品。在這個(gè)例子中就是,如果那天我有病非要加一個(gè)“幼兒園學(xué)生”進(jìn)來,那您就必須修改這個(gè)“幫手”的代碼了,這個(gè)“幫手”現(xiàn)在就變成Version2(如下圖)了,這個(gè)二版的幫手,可以在“代碼”層實(shí)現(xiàn)對(duì)一版(還沒有添加幼兒園學(xué)生之前)的通用,但這種保證在“開-閉”原則看來,還是不夠的,不保險(xiǎn)的,它要的是在類的結(jié)構(gòu)上的保證。聲明一下,這是我很感性的理解,不正確的可能性很高。

5. 讓“幫手”也多態(tài)
這里可以嘗試讓“幫手”也多態(tài)一下,這樣“每種學(xué)生類創(chuàng)建的任務(wù)”都被分派到了多態(tài)出來的類中去了。這時(shí),再有新的學(xué)生類型加進(jìn)來,添加一個(gè)對(duì)應(yīng)的幫手就可以了。這樣雖然類多了一些,但是符合“開-閉”原則,書上稱之為“工廠方法模式”。如圖:

假如Client現(xiàn)在要使用一個(gè)小學(xué)生,代碼如下:
1
//創(chuàng)建一個(gè)小學(xué)生工廠類這個(gè)幫手
2
Student_Factory factory = new Small_Student_Factory();
3
//求助這個(gè)幫手,幫我創(chuàng)建一個(gè)
4
Student primaryStudent = factory.produce();
5
//這時(shí)就可以使用這個(gè)小學(xué)生了,讓它玩玩游戲吧
6
primaryStudent.playGames();
7
在這里還是要強(qiáng)調(diào)兩點(diǎn):
n 雖然實(shí)際上Client的確使用了一個(gè)小學(xué)生對(duì)象(Small_Student),但這里Client也認(rèn)為它就是Student對(duì)象,這里一定要用Student接口來隱藏它的具體類。
n 另外,卻不需要用Student_Factory這個(gè)接口來隱藏它的具體類,因?yàn)椋?/SPAN>Client實(shí)際就是通過“選擇它的具體類”這招兒來“選擇創(chuàng)建的學(xué)生類型”。這里的Student_Factory更多的功能不是“隱藏”具體類,而是“規(guī)范”具體類。
項(xiàng)目實(shí)踐
扯淡到此,該聯(lián)系我們的項(xiàng)目啦。
由于是做研究生院的項(xiàng)目,其中巨大的需求就是要讓同學(xué)能在網(wǎng)上提交各種申請(qǐng)單,申請(qǐng)退學(xué)的,申請(qǐng)轉(zhuǎn)專業(yè)的,申請(qǐng)復(fù)學(xué)的,申請(qǐng)保留學(xué)籍的,除了申請(qǐng)女朋友的外,應(yīng)有盡有。 對(duì)于這些單子,用最最基本OO思維,根據(jù)共性個(gè)性分析方式,抽象出一個(gè)申請(qǐng)單接口,和若干的具體類。
當(dāng)然,除了概念上感性上吻合以外,在項(xiàng)目中它們也要“真”的有巨大的共性才行,如都要提交,修改,刪除,審核,打印等功能。
靠,既然都這樣了,肯定就用一接口規(guī)定它了。
想到這里,加上有了上面的思考,不用說了,就用工廠方法模式啦。

圖中Ent_Shift就是申請(qǐng)單接口。等于前面分析的Student接口。還可以看到有很多具體類,前面例子中是代表各種學(xué)生,這里就是代表各種申請(qǐng)單。
當(dāng)然,這里還有很多工廠,也就是前面一直叫的“幫手”。

Bean_Shift就是工廠接口,相當(dāng)于前面的Student_Factory接口。還有很多的具體類就是生產(chǎn)各種申請(qǐng)單的工廠類。
下面就是使用“申請(qǐng)單工廠方法模式”的一段客戶端代碼:
1
//聲明申請(qǐng)單接口
2
Ent_Shift es = null;
3
//聲明申請(qǐng)單工廠接口
4
Bean_Shift bs = null;
5
//根據(jù)傳入的申請(qǐng)單類型數(shù)字身成對(duì)應(yīng)的申請(qǐng)單工廠
6
switch (shiftTypeID)
{
7
case Bean_Shift.SHIFT_CHANGETEAAP :
8
bs = new Bean_Shift_CHANGETEAAP();
9
break;
10
case Bean_Shift.SHIFT_RESERVEAP :
11
bs = new Bean_Shift_RESERVEAP();
12
break;
13
case Bean_Shift.SHIFT_RENEWAP :
14
bs = new Bean_Shift_RENEWAP();
15
break;
16
//省略了別的申請(qǐng)單……………………
17
default :
18
this.forwardErr(req, resp, "所選擇的異動(dòng)類別不存在");
19
}
20
21
try
{
22
//調(diào)用工廠接口的生產(chǎn)方法
23
es = bs.getBlankShift(stuID);
24
25
} catch (Exception e)
{
26
this.forwardErr(req, resp, DB_ERROR);
27
}
28
//調(diào)用單子的提交方法
29
es.submit(req);
30
31
//發(fā)給頁面去顯示
32
es.fowrardWithSessionObject(
33
req,
34
resp,
35
Ent_Shift.nameInSessionAndRequest);
36
升華
個(gè)人比較同意《Design Pattern Explained》中作者講的,要用好很多的模式,其中都有一個(gè)思路,就是用接口或抽象類來隱藏子類的不同。
我每當(dāng)看到這時(shí),老是會(huì)被一種思路困擾――“new只能new具體類啊,這咋能隱藏呢,這隱藏還有什么用呢?”。
作者仿佛也曾經(jīng)有過我的這個(gè)傻B苦惱,它的解決方法就是:根本在使用對(duì)象的時(shí)候,特別是設(shè)計(jì)階段,盡量不去想對(duì)象是在那里被new的。他認(rèn)為:反正有了工廠模式后,你總有辦法把他們new出來的。
所以,我用了工廠模式后更發(fā)的啟發(fā)是:以后設(shè)計(jì)的時(shí)候不要想一個(gè)Client怎么創(chuàng)建一個(gè)對(duì)象,盡管放心大膽的先繼續(xù)想,直接使用就好了。反正最后我還有工廠模式呢。
因此俺的副標(biāo)題才是“Ignore how they were created”,呵呵。
MARCO ZHANG 2006年2月16日3:52:10 