依赖注入(Injecting dependencies)经常听起来会让人感觉到很难以理解,会让大家感觉这是很复杂的编程技术,但是事实上并不是这样,依赖注入非常方便使用,它会让你的程序非常便于理解,同时也更容易进行测试。
依赖注入的工作方式:
任何好的程序都是由很多互相协作的类来实现复 杂的逻辑的。在传统的使用方法中,每个对象都会把自己的引用传递给它协作的对象,但是这样会造成对象间的高度耦合,同时不容易对程序进行测试。接下来我们看一个例子:
从这个例子中,我们可以看出来,DamselRescuingKnight在容器中创造了他自己的 RescueDamselQuest请求,不过这使得DamselRescuingKnight和 RescueDamselQuest紧紧耦合,同时也限制了骑士能做的事情,如果美少女需要救援,骑士可以轻松胜任,但是如果恶龙需要治服,或者...其他各种事情,这时骑士根本无能为力。除此之外,这样的设计也很难去进行测试,在这个例子中当 embarkOnQuest()被调用,又调用到 embark()到时,需要我们在 embark()中插入断点,但是这很难实现,也就是说我们没法很好的测试DamselRescuingKnight的功能。
耦合是一个双头怪兽,一方面,它高耦合的代码很难测试,很难复用,同时也很难理解。而且它还可能造成whack-a-mole这样的bug(就像我们在打鼹鼠时一样,我们注重一处的代码,可是在其他地方却引发了更多的bug),另一方面,其实一些适当的耦合其实也是有用的,完全的飞耦合让我们很难实现有些功能。为了能够高效的开发,类之间必须能够互相了解, 适当的耦合是必须的,但是必须要控制好这个度。通过使用DI,对象所需要的依赖会在创建时由熟悉各个对象的第三方提供,也就是将每个对象需要的依赖在它在创建时注入给它。接下来我们将看到一个真正全能的骑士,他不仅能够击败邪恶势力拯救美少女,同时也能够下厨做美食,他能够回应你的各种请求。
如上面所示,BraveKnight并不像DamselRescuingKnight,它不需要创建任何请求,取而代之的是在构造器中或获取它需要的request。这种DI一般被称为constructor injection。不仅如此,上面的quest如果是个接口,那么我们可以实现任何形式的request。
重点是BraveKnight不需要和任何已实现的Quest耦合,它并不需要去了解这个接口是如何实现的。这就是DI最重要的特性-----低耦合性。这个特性的一个很重要的用途是我们在进行测试时可以可mock掉这个接口。而在高耦合的设计中这很难实现。
在这里,使用了叫做Mockitode的mock对象framework,通过它,我们可以创造一个实现了Quest接口的mock,利用来实例化BraveKnight,并且通过构造函数注入mockquest,这时我们就可以调用embarkOnQuest()方法,并且去验证embark()是否确实被调用了一次。