也許在各位讀者眼里,依賴注入和框架Spring等同的。其實(shí)依賴注入是一個(gè)可以在容器外使用的OO開發(fā)概念。同時(shí),依賴注入(Dependency Injection)對(duì)于單元測(cè)試
也很有用。在該篇blog中,我們可以了解到:
- 什么是依賴注入(Dependency Injection)
- 如何保證類和依賴注入友好性
- 依賴注入有助于單元測(cè)試
女士們先生們,跟我一起來(lái)暢游吧。
一輛簡(jiǎn)單的車
讓我們用引擎和車來(lái)構(gòu)造一個(gè)簡(jiǎn)單的例子。為了清晰我們使類和接口程序體為空。
一輛車有引擎,而且我們希望這輛車裝配路虎所用的引擎MooseEngine。
我們先配置引擎:
public interface Engine {
}
public class SlowEngine implements Engine {
}
public class FastEngine implements Engine {
}
public class MooseEngine implements Engine {
}
接著我們建立車:
public class Car {
private MooseEngine engine;
}
盡管這是一輛很好的車,但它并不能有任何規(guī)格的引擎,即使市場(chǎng)上有其他品牌的
車可以得到。我們說(shuō)車類同路虎引擎類是緊耦合的。如果我們決定裝配另一種引擎
,會(huì)發(fā)生什么?
用接口編程
你可能看到路虎引擎MooseEngine™已經(jīng)實(shí)現(xiàn)了引擎接口。其它品牌引擎也實(shí)現(xiàn)了
相同的接口。讓我們思考一下,當(dāng)我們?cè)O(shè)計(jì)我們的車Car類時(shí),我們認(rèn)為一個(gè)
"Car"裝配一個(gè)引擎。所以讓我們重寫一下Car類,讓它使用接口代替具體的引擎
引用:
public class Car {
private Engine engine;
}
用接口編程在依賴注入中是一個(gè)重要的概念。你可能說(shuō)這用了一個(gè)接口,那么具體
類在哪呢?你在哪給它賦值?若我想我的車用路虎引擎MooseEngine,我可以用以下方式給它賦值:
public class Car {
private Engine engine = new MooseEngine();
}
但是這有用么?這看起來(lái)和第一個(gè)方法沒什么不同。我們的車還是緊緊捆綁在路虎 MooseEngine引擎上,這個(gè)公司倒閉了呢?我們?nèi)ツ抢镎业轿覀兊囊妗?/p>
介紹依賴注入(Dependency Injection)
正如名字所示,依賴注入是關(guān)于注入依賴的,或者說(shuō)設(shè)置實(shí)例間的關(guān)系的。有些人
引用好萊塢的格言解釋它:"不要打電話給我,我會(huì)打給你"。我喜歡叫他"強(qiáng)
奸"原理:"我不關(guān)心你是誰(shuí),我只想你照我的要求做"。在我們第一個(gè)的例子
中,Car類依賴于叫MooseEngine的引擎具體類。當(dāng)一個(gè)類A一來(lái)另一個(gè)類B并且B的
實(shí)現(xiàn)直接在A中得到引用時(shí),我們說(shuō)類A緊耦合類B。正如我們?cè)诘诙嬷锌吹降?我
們已經(jīng)決定使用Engine接口代替MooseEngine具體類使Car更具靈活性。更進(jìn)一步我
們決定不定義引擎的具體實(shí)現(xiàn)。換句話說(shuō),我們使Car類松散耦合。Car類不再依賴
于任何引擎的具體類。那么我們哪里定義我們用的是哪一個(gè)引擎呢?這就是依賴注
入發(fā)揮作用的地方。我們將從外面注入具體引擎類,代替在Car類中引用具體引擎
類。如何做到呢?繼續(xù)GO!
1.使用基于構(gòu)造方法的注入
建立依賴的一個(gè)方式是通過傳遞依賴的具體實(shí)現(xiàn)給構(gòu)造方法來(lái)建立依賴。我們的Car類將變
為:
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
到那時(shí)我們能使用任何類型的引擎構(gòu)建一輛車。例如,一輛使用偉大的路虎 MooseEngine而另一輛使用劣質(zhì)的SlowEngine:
public class Test {
public static void main(String[] args) {
Car myGreatCar = new Car(new MooseEngine());
Car hisCrappyCar = new Car(new SlowEngine());
}
}
2. 基于setter方法的injection
建立依賴的另一種方法是使用setter方法。當(dāng)很多依賴需要注入時(shí),推薦使用setter方
法。我們的Car類這時(shí)又變成了樣:
public class Car {
private Engine engine;
public void setEngine(Engine engine) {
this.engine = engine;
}
}
它看起來(lái)和基于構(gòu)造方法注入長(zhǎng)得差不多。我們使用以下方式實(shí)現(xiàn)相同的Car類:
public class Test {
public static void main(String[] args) {
Car myGreatCar = new Car();
myGreatCar.setEngine(new MooseEngine());
Car hisCrappyCar = new Car();
hisCrappyCar.setEngine(new SlowEngine());
}
}
單元測(cè)試中用依賴注入
如果你用Car類的第一版和基于setter方法注入的比較,那么你可能認(rèn)為使用依賴注入需要使用一些額外的操作。這么說(shuō)沒錯(cuò)。你必須寫一個(gè)setter方法。但是當(dāng)你進(jìn)行測(cè)試時(shí),就會(huì)發(fā)現(xiàn)這些額外操作很有好處。若你對(duì)什么是單元測(cè)試一臉茫然的話,
你最好看一看《Unit in Action》這本書。我們的車?yán)雍芎?jiǎn)單,所以不能看出單元
測(cè)試的依賴注入多么有用。 我們下車,考慮一個(gè)營(yíng)火會(huì)的例子,特別是在單元測(cè)試
中使用mock這部分。我們有一個(gè)用遠(yuǎn)程EJB注冊(cè)農(nóng)場(chǎng)動(dòng)物的的servlet類 。
public class FarmServlet extends ActionServlet {
public void doAction( ServletData servletData ) throws Exception {
String species = servletData.getParameter("species");
String buildingID = servletData.getParameter("buildingID");
if ( Str.usable( species ) && Str.usable( buildingID ) ) {
FarmEJBRemote remote = FarmEJBUtil.getHome().create();
remote.addAnimal( species , buildingID );
}
}
}
你應(yīng)該也注意到FarmServlet是緊耦合于FarmEJBRemote實(shí)例,通過調(diào)用"FarmEJBUtil.getHome().create()"解析該實(shí)例。這樣做很難進(jìn)行測(cè)試。當(dāng)進(jìn)行單元
測(cè)試的時(shí)候,我們不想用任何數(shù)據(jù)庫(kù)。我們也不想訪問一個(gè)EJB服務(wù)器。這使進(jìn)行
單元測(cè)試非常難以執(zhí)行并且很慢。所以為了順利進(jìn)行FarmServlet 類的單元測(cè)試,我
們使它成為松散耦合的。為了移除FarmServlet和FarmEJBRemote的強(qiáng)依賴 ,我們用
基于setter方法的注入:
public class FarmServlet extends ActionServlet {
private FarmEJBRemote remote;
public void setRemote(FarmEJBRemote remote) {
this.remote = remote;
}
public void doAction( ServletData servletData ) throws Exception {
String species = servletData.getParameter("species");
String buildingID = servletData.getParameter("buildingID");
if ( Str.usable( species ) && Str.usable( buildingID ) ) {
remote.addAnimal( species , buildingID );
}
}
}
在真實(shí)的部署包中,我們將確定FarmServlet的遠(yuǎn)程成員的實(shí)例通過使用
"FarmEJBUtil.getHome().create()"已經(jīng)被注入了。在我們的單元測(cè)試中,我們生成
一個(gè)moke類模擬FarmEJBRemote。換句話說(shuō),就是我們將用一個(gè)moke類實(shí)現(xiàn)
FarmEJBRemote:
class MockFarmEJBRemote implements FarmEJBRemote {
private String species = null;
private String buildingID = null;
private int nbCalls = 0;
public void addAnimal( String species , String buildingID )
{
this.species = species ;
this.buildingID = buildingID ;
this.nbCalls++;
}
public String getSpecies() {
return species;
}
public String getBuildingID() {
return buildingID;
}
public int getNbCalls() {
return nbCalls;
}
}
public class TestFarmServlet extends TestCase {
public void testAddAnimal() throws Exception {
// mock操作和一個(gè)FarmEJBRemote相似
MockFarmEJBRemote mockRemote = new MockFarmEJBRemote();
// servlet,我們將mock賦值給遠(yuǎn)程依賴。
FarmServlet servlet = new FarmServlet();
servlet.setRemote(mockRemote);
// 另一個(gè)象ServletData的mock
MockServletData mockServletData = new MockServletData();
mockServletData.getParameter_returns.put("species","dog");
mockServletData.getParameter_returns.put("buildingID","27");
servlet.doAction( mockServletData );
assertEquals( 1 , mockRemote.getNbCalls() );
assertEquals( "dog" , mockRemote.getSpecies() );
assertEquals( 27 , mockRemote.getBuildingID() );
}
}
就到這吧。我們能很容易的測(cè)試FarmServlet。
總結(jié)一下吧:
以上的探討,對(duì)各位同仁有用的話請(qǐng)回復(fù)一下。
凡是有該標(biāo)志的文章,都是該blog博主Caoer(草兒)原創(chuàng),凡是索引、收藏
、轉(zhuǎn)載請(qǐng)注明來(lái)處和原文作者。非常感謝。