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