也許在各位讀者眼里,依賴注入和框架Spring等同的。其實依賴注入是一個可以在容器外使用的OO開發概念。同時,依賴注入(Dependency Injection)對于單元測試
也很有用。在該篇blog中,我們可以了解到:
- 什么是依賴注入(Dependency Injection)
- 如何保證類和依賴注入友好性
- 依賴注入有助于單元測試
女士們先生們,跟我一起來暢游吧。
一輛簡單的車
讓我們用引擎和車來構造一個簡單的例子。為了清晰我們使類和接口程序體為空。
一輛車有引擎,而且我們希望這輛車裝配路虎所用的引擎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;
}
盡管這是一輛很好的車,但它并不能有任何規格的引擎,即使市場上有其他品牌的
車可以得到。我們說車類同路虎引擎類是緊耦合的。如果我們決定裝配另一種引擎
,會發生什么?
用接口編程
你可能看到路虎引擎MooseEngine™已經實現了引擎接口。其它品牌引擎也實現了
相同的接口。讓我們思考一下,當我們設計我們的車Car類時,我們認為一個
"Car"裝配一個引擎。所以讓我們重寫一下Car類,讓它使用接口代替具體的引擎
引用:
public class Car {
private Engine engine;
}
用接口編程在依賴注入中是一個重要的概念。你可能說這用了一個接口,那么具體
類在哪呢?你在哪給它賦值?若我想我的車用路虎引擎MooseEngine,我可以用以下方式給它賦值:
public class Car {
private Engine engine = new MooseEngine();
}
但是這有用么?這看起來和第一個方法沒什么不同。我們的車還是緊緊捆綁在路虎 MooseEngine引擎上,這個公司倒閉了呢?我們去那里找到我們的引擎。
介紹依賴注入(Dependency Injection)
正如名字所示,依賴注入是關于注入依賴的,或者說設置實例間的關系的。有些人
引用好萊塢的格言解釋它:"不要打電話給我,我會打給你"。我喜歡叫他"強
奸"原理:"我不關心你是誰,我只想你照我的要求做"。在我們第一個的例子
中,Car類依賴于叫MooseEngine的引擎具體類。當一個類A一來另一個類B并且B的
實現直接在A中得到引用時,我們說類A緊耦合類B。正如我們在第二版中看到的,我
們已經決定使用Engine接口代替MooseEngine具體類使Car更具靈活性。更進一步我
們決定不定義引擎的具體實現。換句話說,我們使Car類松散耦合。Car類不再依賴
于任何引擎的具體類。那么我們哪里定義我們用的是哪一個引擎呢?這就是依賴注
入發揮作用的地方。我們將從外面注入具體引擎類,代替在Car類中引用具體引擎
類。如何做到呢?繼續GO!
1.使用基于構造方法的注入
建立依賴的一個方式是通過傳遞依賴的具體實現給構造方法來建立依賴。我們的Car類將變
為:
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
到那時我們能使用任何類型的引擎構建一輛車。例如,一輛使用偉大的路虎 MooseEngine而另一輛使用劣質的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方法。當很多依賴需要注入時,推薦使用setter方
法。我們的Car類這時又變成了樣:
public class Car {
private Engine engine;
public void setEngine(Engine engine) {
this.engine = engine;
}
}
它看起來和基于構造方法注入長得差不多。我們使用以下方式實現相同的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());
}
}
單元測試中用依賴注入
如果你用Car類的第一版和基于setter方法注入的比較,那么你可能認為使用依賴注入需要使用一些額外的操作。這么說沒錯。你必須寫一個setter方法。但是當你進行測試時,就會發現這些額外操作很有好處。若你對什么是單元測試一臉茫然的話,
你最好看一看《Unit in Action》這本書。我們的車例子很簡單,所以不能看出單元
測試的依賴注入多么有用。 我們下車,考慮一個營火會的例子,特別是在單元測試
中使用mock這部分。我們有一個用遠程EJB注冊農場動物的的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 );
}
}
}
你應該也注意到FarmServlet是緊耦合于FarmEJBRemote實例,通過調用"FarmEJBUtil.getHome().create()"解析該實例。這樣做很難進行測試。當進行單元
測試的時候,我們不想用任何數據庫。我們也不想訪問一個EJB服務器。這使進行
單元測試非常難以執行并且很慢。所以為了順利進行FarmServlet 類的單元測試,我
們使它成為松散耦合的。為了移除FarmServlet和FarmEJBRemote的強依賴 ,我們用
基于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 );
}
}
}
在真實的部署包中,我們將確定FarmServlet的遠程成員的實例通過使用
"FarmEJBUtil.getHome().create()"已經被注入了。在我們的單元測試中,我們生成
一個moke類模擬FarmEJBRemote。換句話說,就是我們將用一個moke類實現
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操作和一個FarmEJBRemote相似
MockFarmEJBRemote mockRemote = new MockFarmEJBRemote();
// servlet,我們將mock賦值給遠程依賴。
FarmServlet servlet = new FarmServlet();
servlet.setRemote(mockRemote);
// 另一個象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() );
}
}
就到這吧。我們能很容易的測試FarmServlet。
總結一下吧:
-
使用接口代替使用具體類來說明一個依賴。
-
避免在類中顯式設置一個依賴的具體實現。
- 賦值依賴的具體實現可能有多種方式,包括基于構造方法或者setter方法的注
入。
- 依賴注入可以讓單元測試增加靈活性。
以上的探討,對各位同仁有用的話請回復一下。
凡是有該標志的文章,都是該blog博主Caoer(草兒)原創,凡是索引、收藏
、轉載請注明來處和原文作者。非常感謝。