关于依赖注入
摘自 Spring In Action 一书,第一章第一节。
DI (dependency injection)并非什么高深莫测的词,下面通过一些简单的例子,来看看到底 DI 是什么,以及它是如何在开发中起到关键作用的。
1 DI 的实现原理
现代的面相对象方法开发的程序,肯定都是由若干个类集合到一起构成的。这些类相互之间进行配合,从而实现预定的业务逻辑。
在传统的开发观点下,每个类都是由自己来获得这些和自己一起协作的类的实例(也就是类自己创建或通过某种方式主动获取到它自己的依赖),但这样的代码存在的问题是高耦合,难测试。
比如下面这段代码:
internal interface IKnight
{
void embarkOnQuest();
}
internal class DamselRescuingKnight : IKnight
{
private RescueDamselQuest quest;
DamselRescuingKnight()
{
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest()
{
quest.embark();
}
}
上面的这段代码中,DamselRescuingKnight
和 RescueDamselQuest
就是紧耦合在一起的,同时这段代码也非常难于维护和测试。特别是当测试 embarkOnQuest
方法的时候,还需要同时去测试 embark 方法。
简单来讲,就是这样的依赖生成和使用方式都是错误的。
所以需要寻找一种手段,让代码低耦合,而 DI 就是这样的一种方式。
在使用 DI 的情况下,可以将对象的依赖在该对象初始化的时候由第三方提供,而非由对象自己主动创建。
下面就来看一个例子:
internal class BraveKnight : IKnight
{
private IQuest quest;
internal BraveKnight(IQuest quest)
{
this.quest = quest;
}
public void embarkOnQuest()
{
quest.embark();
}
}
上述代码中,依赖是通过构造时注入的,此时注入的对象以接口类型来表示,从而形成模块边界,当前的 BraveKnight 不再依赖任何的实现,而是依赖于接口抽象,从而实现低耦合。
这样的代码在单元测试的时候也非常方便,由于依赖于接口,从而可以对实现进行灵活替换。
下面为了注入 IQuest 对象,首先来提供一个 IQuest 的实现类 SlayDragonQuest:
internal class SlayDragonQuest : IQuest
{
public void embark()
{
Console.WriteLine("Embarking on quest to slay the dragon!");
}
}
那如何将这个 IQuest 实现类注入到 BraveKnight 中呢?
这里就需要一个概念,即 wiring 了。在 Spring 中通过 XML 配置的方式将依赖实现方和依赖需求方进行绑定,而在其他地方没有这样的配置机制的情况下,就需要其他的措施来对依赖自动进行注入了。这里需要去寻找一些依赖注入的手段。
在没有依赖注入工具帮助的前提下,可以手动在需要建立对象的时候指定依赖,而依赖的创建就是第三方完成的。
在日常编程的时候,首先第一个习惯就是把依赖由依赖具体实现变为依赖接口。SOLID:单一职责,开放封闭,里式替换,依赖倒置,迪米特。其中的依赖倒置就是说的依赖抽象,而非依赖具体实现,而依赖倒置的一种实现方式就是采用依赖注入的手段。
最后,现在写代码的目标就是先达到 SOLID,然后往整洁流派迈进,慢慢地提高在架构上的功力,最后再求融会贯通!