javaeye上看到有帖子,置疑spring和依賴注入的價(jià)值,回復(fù)內(nèi)容整理如下:
依賴注入對(duì)設(shè)計(jì)有利,而spring則促進(jìn)了依賴注入的使用。
如果業(yè)務(wù)處理類,它所使用的倚賴,都是依靠在這個(gè)類內(nèi)部實(shí)現(xiàn)或者查找,那么必然使得正常的業(yè)務(wù)邏輯和獲取依賴的方法混在一起。
我取個(gè)最簡(jiǎn)單的場(chǎng)景,某個(gè)注冊(cè)的工作類,它需要獲取當(dāng)前"容許的用戶名的最大長(zhǎng)度",這個(gè)依賴非常簡(jiǎn)單吧?基本每個(gè)注冊(cè)類都有這個(gè)限制,我們現(xiàn)在
把場(chǎng)景考慮的全面一點(diǎn),對(duì)于復(fù)雜一點(diǎn)的系統(tǒng),這個(gè)最大長(zhǎng)度的限制可能來(lái)源很多,比如配制文件,數(shù)據(jù)庫(kù),可能類工作在前臺(tái)比如web而配制在后臺(tái),可能需要
和第三放系統(tǒng)一起工作而需要到第三方系統(tǒng)中獲取而對(duì)方只提供web service...
這么一個(gè)簡(jiǎn)單的依賴,“用戶名的最大長(zhǎng)度”,如果用依賴注入,只要一個(gè)簡(jiǎn)單的setUsernameMaxLength()方法就可以搞定。考慮
上面那么多種可能都出現(xiàn),最惡劣的情況是要求一個(gè)系統(tǒng)可以同時(shí)支持然后通過(guò)配制方式進(jìn)行,這對(duì)于將一個(gè)產(chǎn)品賣給n家客戶的公司來(lái)說(shuō)是最正常不過(guò)的要求。
那么我們來(lái)看一個(gè)很有"spring"風(fēng)格的采用依賴注入的設(shè)計(jì),通常都將會(huì)是這樣:
注冊(cè)工作類:
public void RegisterWork {
public void setUsernameMaxLengthProvider(UsernameMaxLengthProvider provider) {
int maxLength = provider.getUsernameMaxLength(); //簡(jiǎn)單獲取結(jié)果,不管provider細(xì)節(jié)
}
....
}
“用戶名的最大長(zhǎng)度”的提供者接口
public interfacd UsernameMaxLengthProvider {
public int getUsernameMaxLength();
}
“用戶名的最大長(zhǎng)度”的提供者則可以有以下實(shí)現(xiàn),都只負(fù)責(zé)讀取數(shù)據(jù),不關(guān)心數(shù)據(jù)被誰(shuí)使用,怎么使用:
1.直接提供,可以用spring 構(gòu)造函數(shù)方式注入usernameMaxLength值,也可以junit測(cè)試時(shí)直接new DirectdProvide對(duì)象
public class DirectdProvider implements UsernameMaxLengthProvider {
private int usernameMaxLength = 10;
public int getUsernameMaxLength() {
return UsernameMaxLength;
}
public DirectdProvider (int usernameMaxLength) {
this.usernameMaxLength = usernameMaxLength;
}
}
2.讀本地配制文件
public class LocalConfigFileProvider implements UsernameMaxLengthProvider {
public void read(File configFile) {
usernameMaxLength = ....
}
}
3.類似的從數(shù)據(jù)庫(kù)讀取 DatabaseProvide
4.類似的web service從第三方讀取 WebServiceProvider
5.其他的可能擴(kuò)展的方式
開(kāi)發(fā)時(shí)邏輯清晰,代碼可讀性超強(qiáng),基本看類名和函數(shù)名搞定。測(cè)試時(shí),輕松測(cè)試RegisterWork,testcase中只要
worker.setUsernameMaxLengthProvider(new
DirectdProvider(20))就搞定。而針對(duì)UsernameMaxLengthProvider的幾個(gè)實(shí)現(xiàn)類,更是輕松使用junit,每
個(gè)都覆蓋一遍。
呵呵,現(xiàn)在我們可以砍刀,最簡(jiǎn)單的一個(gè)int型的“用戶名的最大長(zhǎng)度”,都可能遭遇如此復(fù)雜的場(chǎng)景。如果不用倚賴注入,而是選擇在RegisterWork中自己搞定“用戶名的最大長(zhǎng)度”的獲取,那么可能要遇到以下問(wèn)題:
1. RegisterWork極其復(fù)雜,可以想像類似的依賴肯定還有其他
2. 獲取“用戶名的最大長(zhǎng)度”的方式和注冊(cè)的業(yè)務(wù)處理邏輯完全沒(méi)有直接聯(lián)系,對(duì)注冊(cè)過(guò)程來(lái)說(shuō)它只關(guān)注結(jié)果,“用戶名的最大長(zhǎng)度”是10還是20,而不是到底讀本地文件還是讀數(shù)據(jù)庫(kù)。喧賓奪主了,次要邏輯干擾了主要邏輯
3. 難于測(cè)試。獲取“用戶名的最大長(zhǎng)度”的方式的這些代碼,被藏在RegisterWork類中,而且很有可能是private方法不對(duì)外暴露(如果不依賴注入還需要暴露嗎?暴露給誰(shuí)呢),怎么用mock測(cè)試?怎么能保證以上幾種的實(shí)現(xiàn)都覆蓋到?
4. 難于擴(kuò)展。就算上面都搞定了,某一天突然來(lái)了一個(gè)變態(tài)需求,要求用ldap從另一個(gè)地方取配置呢?難道再把ldap請(qǐng)求的那一大片代碼也寫到RegisterWork里面?
5.
更難于被第三方擴(kuò)展。運(yùn)氣好,上面這個(gè)ldap取配制的變態(tài)需求客戶開(kāi)始沒(méi)有要求。順利開(kāi)發(fā)完成測(cè)試通過(guò)然后準(zhǔn)備上線,最后一晚了客戶才發(fā)現(xiàn),"哦,給忘
了,你們想辦法給加上,快,快,明天一早就要上線運(yùn)行了...你們寫死在代碼里面了?那只能你們修改了原代碼了"。吐血了吧,先罵一頓,可是活還的干啊,
咬牙切齒的把新的實(shí)現(xiàn)代碼加上了,還得編譯打包更新重啟...如果是spring多好,單獨(dú)寫一個(gè)LdapProvider類,測(cè)試(這個(gè)測(cè)試比雜在
RegisterWork里面測(cè)試簡(jiǎn)單的多)通過(guò)后單獨(dú)提供這個(gè)class仍classpath下,修改spring的配制將原來(lái)的
***Provider替換掉,輕松搞定,甚至可以把這活仍給客戶的開(kāi)發(fā)人員,告訴他們?cè)趺刺鎿Q就可以了,管你ladp還是其他,誰(shuí)讓你們需求不明確,自
己擴(kuò)展去。
6.
容易出錯(cuò)。剛吐血完成上面的變態(tài)需求,更新完畢,一會(huì)客戶電話來(lái)了,“...怎么...不正常了?”。又吐血幾升地檢查,終于找出來(lái)了,原來(lái)是剛才寫
ladp訪問(wèn)的代碼時(shí)不小心改錯(cuò)了RegisterWork的一個(gè)地方,誰(shuí)讓RegisterWork類有幾十上百個(gè)方法好幾千行呢,一時(shí)急,又沒(méi)有測(cè)試
到......可是客戶不會(huì)理解的。
上述的場(chǎng)景,spring + 依賴注入的設(shè)計(jì)方式,優(yōu)點(diǎn)很明顯吧。
再考慮一下維護(hù)和二次開(kāi)發(fā)的問(wèn)題,上面的spring + 依賴注入的代碼,好看易懂,方便擴(kuò)展,維護(hù)起來(lái)輕松。如果是那么堆在RegisterWork里面,在那個(gè)大堆中代碼要找出這些代碼并讀懂,估計(jì)不是件輕松的事情。
代碼維護(hù)是需要成本的,寫出易于維護(hù)的代碼,是一個(gè)優(yōu)秀程序員的基本素養(yǎng),至少,不能讓下一個(gè)接手的人罵娘吧?