創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、成都外貿(mào)網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的閔行網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
首先我們考慮一個(gè)簡(jiǎn)單的例子,這里我們使用engine 類和car 類。為了更加清楚的描述問題,我們將類和接口都置空。每輛car會(huì)有一個(gè)engine,我們想給car裝備上著名的MooseEngine。
Engine類如下:
1 public interface Engine { 2 3 } 4 5 public class SlowEngine implements Engine { 6 7 } 8 9 public class FastEngine implements Engine {10 11 }12 13 public class MooseEngine implements Engine {14 15 }
然后我們可以得到一個(gè)car類:
1 public class Car {2 3 private MooseEngine engine;4 5 }
這是一輛非常棒的汽車,但是即使有其他種類的引擎上市,我們也不能裝備這些引擎了。我們說這里的car類和MooseEngine類是緊耦合的(tightly coupled)。雖然MooseEngine很棒,但是如果我們想把它換成別的引擎呢?
回到頂部
你可能已經(jīng)注意到了MooseEngine實(shí)現(xiàn)了Engine接口。其它引擎也實(shí)現(xiàn)了同樣的接口。我們可以想一想,當(dāng)我們?cè)O(shè)計(jì)我們的Car類時(shí),我們想讓一輛“car”裝備一個(gè)“engine”。所以我們重新實(shí)現(xiàn)一個(gè)Car類,這次我們使用Engine接口:
1 public class Car {2 3 private Engine engine;4 5 }
接口編程是依賴注入中的一個(gè)很重要的概念。我聽到了你的尖叫,“等一下,你在這里使用接口,具現(xiàn)類(concrete class)該怎么辦?你在哪里設(shè)置(set)引擎?我想在我的汽車中裝備MooseEngine”。我們可以按下面的方式來設(shè)置它:
1 public class Car {2 3 private Engine engine = new MooseEngine();4 5 }
但這就是有用的么?它看上去和第一個(gè)例子沒有多大區(qū)別。我們的car仍然同MooseEngine是緊耦合的。那么,我們?cè)撊绾卧O(shè)置(set或者說注入(inject))我們的汽車引擎呢?
回到頂部
就像依賴注入這個(gè)名字一樣,依賴注入就是注入依賴,或者簡(jiǎn)單的說,設(shè)置不同實(shí)例之間的關(guān)系。一些人將它同好萊塢的一條規(guī)矩關(guān)聯(lián)了起來,“不要給我打掉話,我打給你?!蔽腋矚g叫它“bugger”法則:“我不關(guān)心你是誰,按我說的做?!痹谖覀兊牡谝粋€(gè)例子中,Car依賴的是Engine的具現(xiàn)類MooseEngine。當(dāng)一個(gè)類A依賴于另外一個(gè)類B的時(shí)候,類B的實(shí)現(xiàn)直接在類A中設(shè)置,我們說A緊耦合于B。第二個(gè)例子中,我們決定使用接口來代替 具現(xiàn)類MooseEngine,這樣就使得Car類更加靈活。并且我們決定不去定義engine的具現(xiàn)類實(shí)現(xiàn)。換句話說,我們使Car類變?yōu)樗神詈?loosely coupled)的了。Car不再依賴于任何引擎的具現(xiàn)類了。那么在哪里指定我們需要使用哪個(gè)引擎呢?依賴注入該登場(chǎng)了。我們不在Car類中設(shè)置具現(xiàn)化的Engine類,而是從外面注入。這又該如何實(shí)現(xiàn)呢?
設(shè)置依賴的一種方法是把依賴類的具體實(shí)現(xiàn)傳遞給構(gòu)造函數(shù)。Car類將會(huì)變成下面這個(gè)樣子:
1 public class Car { 2 3 private Engine engine; 4 5 public Car(Engine engine) { 6 7 this.engine = engine; 8 9 }10 11 }
然后我們就可以用任何種類的engine來創(chuàng)建Car了。例如,一個(gè)car使用MooseEngine,另外一個(gè)使用crappy SlowEngine:
1 public class Test { 2 3 public static void main(String[] args) { 4 5 Car myGreatCar = new Car(new MooseEngine()); 6 7 Car hisCrappyCar = new Car(new SlowEngine()); 8 9 }10 11 }
另外一種設(shè)置依賴的普通方法就使用setter方法。當(dāng)需要注入很多依賴的時(shí)候,建議使用setter方法而不是構(gòu)造函數(shù)。我們的car類將會(huì)被實(shí)現(xiàn)成下面的樣子:
1 public class Car { 2 3 private Engine engine; 4 5 public void setEngine(Engine engine) { 6 7 this.engine = engine; 8 9 }10 11 }
它和基于構(gòu)造函數(shù)的依賴注入非常類似,于是我們可以用下面的方法來實(shí)現(xiàn)上面同樣的cars:
1 public class Test { 2 3 public static void main(String[] args) { 4 5 Car myGreatCar = new Car(); 6 7 myGreatCar.setEngine(new MooseEngine()); 8 9 Car hisCrappyCar = new Car();10 11 hisCrappyCar.setEngine(new SlowEngine());12 13 }14 15 }
回到頂部
如果你將Car類的第一個(gè)例子同使用setter依賴注入的例子進(jìn)行比較,你可能認(rèn)為后者使用了額外的步驟來實(shí)現(xiàn)Car類的依賴注入。這沒錯(cuò),你必須實(shí)現(xiàn)一個(gè)setter方法。但是當(dāng)你在做單元測(cè)試的時(shí)候,你會(huì)感覺到這些額外的工作都是值得的。如果你對(duì)單元測(cè)試不熟悉,推薦你看一下這個(gè)帖子單元測(cè)試有毒 。我們的Car的例子太簡(jiǎn)單了,并沒有把依賴注入對(duì)單元測(cè)試的重要性體現(xiàn)的很好。因此我們不再使用這個(gè)例子,我們使用前面已經(jīng)講述過的關(guān)于篝火故事的例子,特別是在在單元測(cè)試中使用mock中的部分。我們有一個(gè)servlet類,通過使用遠(yuǎn)端EJB來在農(nóng)場(chǎng)中”注冊(cè)”動(dòng)物:
1 public class FarmServlet extends ActionServlet { 2 3 public void doAction( ServletData servletData ) throws Exception { 4 5 String species = servletData.getParameter("species"); 6 7 String buildingID = servletData.getParameter("buildingID"); 8 9 if ( Str.usable( species ) && Str.usable( buildingID ) ) {10 11 FarmEJBRemote remote = FarmEJBUtil.getHome().create();12 13 remote.addAnimal( species , buildingID );14 15 }16 17 }18 19 }
你已經(jīng)注意到了FarmServlet被緊耦合到了FarmEJBRemote實(shí)例中,通過調(diào)用“FarmEJBUtil.getHome().create()”來取回實(shí)例值。這么做會(huì)非常難做單元測(cè)試。當(dāng)作單元測(cè)試的時(shí)候,我們不想使用任何數(shù)據(jù)庫。我們也不想訪問EJB服務(wù)器。因?yàn)檫@不僅會(huì)使單元測(cè)試很難進(jìn)行而且會(huì)使其變慢。所以為了能夠順利的為FarmServlet類做單元測(cè)試,最好使其變成松耦合的。為了清除FarmServlet和FarmEJBRemote之間的緊依賴關(guān)系,我們可以使用基于setter的依賴注入:
1 public class FarmServlet extends ActionServlet { 2 3 private FarmEJBRemote remote; 4 5 public void setRemote(FarmEJBRemote remote) { 6 7 this.remote = remote; 8 9 } 10 11 public void doAction( ServletData servletData ) throws Exception {12 13 String species = servletData.getParameter("species");14 15 String buildingID = servletData.getParameter("buildingID");16 17 if ( Str.usable( species ) && Str.usable( buildingID ) ) {18 19 remote.addAnimal( species , buildingID );20 21 }22 23 }24 25 }
在真實(shí)的部署包中,我們確保通過調(diào)用“FarmEJBUtil.getHome().create()”而創(chuàng)建的一個(gè)FarmServlet遠(yuǎn)端成員實(shí)例會(huì)被注入。在我們的單元測(cè)試中,我們使用一個(gè)虛擬的mock類來模擬FarmEJBRemote。換句話說,我們通過使用mock類來實(shí)現(xiàn)FarmEJBRemote:
1 class MockFarmEJBRemote implements FarmEJBRemote { 2 3 private String species = null; 4 5 private String buildingID = null; 6 7 private int nbCalls = 0; 8 9 public void addAnimal( String species , String buildingID )10 11 {12 13 this.species = species ;14 15 this.buildingID = buildingID ;16 17 this.nbCalls++;18 19 }20 21 public String getSpecies() {22 23 return species;24 25 }26 27 public String getBuildingID() {28 29 return buildingID;30 31 }32 33 public int getNbCalls() {34 35 return nbCalls;36 37 }38 39 }40 41 42 43 public class TestFarmServlet extends TestCase {44 45 public void testAddAnimal() throws Exception {46 47 // Our mock acting like a FarmEJBRemote48 49 MockFarmEJBRemote mockRemote = new MockFarmEJBRemote();50 51 // Our servlet. We set our mock to its remote dependency52 53 FarmServlet servlet = new FarmServlet();54 55 servlet.setRemote(mockRemote);56 57 58 59 // just another mock acting like a ServletData60 61 MockServletData mockServletData = new MockServletData(); 62 63 mockServletData.getParameter_returns.put("species","dog");64 65 mockServletData.getParameter_returns.put("buildingID","27");66 67 68 69 servlet.doAction( mockServletData );70 71 assertEquals( 1 , mockRemote.getNbCalls() );72 73 assertEquals( "dog" , mockRemote.getSpecies() );74 75 assertEquals( 27 , mockRemote.getBuildingID() );76 77 }78 79 }
這樣很容易就能測(cè)試FarmServlet了。