将依赖注入引入到你的项目当中去,你将发现你的代码显著地变得简洁,易懂和易于测试。
任何有实际意义的程序(很有可能比Hello World复杂得多)都由不止一个类组成,这些类彼此交互去实现某种业务逻辑。传统方法是,每个对象都由它自己负责管理与之合作的对象(即它所依赖的对象)的引用。这将导致高度耦合和难以测试的代码。 举例来说,考虑下面这个Knight类:package com.springinaction.knights; public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { quest = new RescueDamselQuest(); } public void embarkOnQuest() throws QuestException { quest.embark(); }}
想必你看到了,DamselRescuingKnight在它的构造函数中自行创建了指示RescueDamselQuest。这使得DamselRescuingKnight被紧密地和RescueDamselQuest耦合到了一起,并因此极大地限制了这个骑士执行指示的能力。如果一个少女需要救援,这个骑士能够召之即来。但是如果要一条恶龙需要杀掉,或者一个圆桌需要……额……滚起来,那么这个骑士只能袖手旁观了。
更糟糕的是,为这个DamselRescuingKnight编写单元测试将出奇地困难。在这个测试当中,你需要保证当骑士的embarkOnQuest()被调用的时候,指示的embark()也要被调用。但是没有一个简单明了的方式,能够实现这一点。所以不幸地,DamselRescuingKnight将是一个待测试的类。耦合是个两难的问题(two-headed beast)。一方面,紧密耦合的代码难以测试,难以复用,难以理解,并且典型地表现出“打地鼠”式的bug特性(修复一个bug,导致出现一个新的甚至更多的bug)。另一方面,一定程度的耦合又是必须的——完全没有任何耦合的代码将一事无成。为了建功立业,不同的类必须以适当的方式交互。总而言之,耦合是必须的,但是应当被小心地维护。
另一方面,通过依赖注入(DI),对象的依赖关系将由系统中负责协调各个对象的第三方组件,在创建对象的时候提供。对象无需去自行创建或管理他们的依赖关系——依赖关系将被注入到需要它们的对象当中。 为了展示这一点,让我们看一看下面这个BraveKnight,这个骑士不仅勇敢,而且能响应任何形式的指示。package com.springinaction.knights; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { this.quest = quest; } public void embarkOnQuest() throws QuestException { quest.embark(); } }
正像你看到的那样,不同于之前的DamselRescuingKnight,BraveKnight没有自行创建指示,而是在构造的时候作为构造器的参数传入指示。这是依赖注入的形式之一,即构造器注入。
更重要的是,他被传入的指示类型是Quest,一个所有指示都必须实现的接口。所以,BraveKnight能够响应RescueDamselQuest,SlayDragonQuest,MakeRoundTableRounderQuest 等传给他的任何其他的Quest的实现。 这里的要点是BraveKnight没有与任何特定的Quest实现发生耦合。对他来说,被要求响应的指示只要实现了Quest接口,那么具体是哪一类型的指示就无关紧要了。这就是依赖注入最大的好处——松耦合。如果一个对象只通过接口(而不是具体实现或初始化的过程)来标明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。 对依赖进行替换的最常用的方法之一,就是在测试的时候用mock实现。你无法充分测试DamselRescuingKnight,因为它是紧耦合的;但是你可以轻松地测试BraveKnight,只需给他一个Quest的mock实现,如下所示:package com.springinaction.knights; import static org.mockito.Mockito.*; import org.junit.Test; public class BraveKnightTest { @Test public void knightShouldEmbarkOnQuest() throws QuestException { Quest mockQuest = mock(Quest.class); BraveKnight knight = new BraveKnight(mockQuest); knight.embarkOnQuest(); verify(mockQuest, times(1)).embark(); } }