从面向对象编程的观点来解释ID:
Collaborating classes should rely on the infrastructure to provide the necessary services.
依赖注入是一系列设计模式和原则,用于开发松散耦合的程序。
最基本的目的:可维护性。
大多数编程技术的目的是尽可能高效率地提供可工作的程序。其中一个目的就是编写可维护性的代码。
使代码容易维护的一种方式就是松散耦合。松散耦合使代码具有可扩展性,可扩展性使代码具有可维护性。
四大误解
DI普遍用于延迟绑定
DI普遍用于单元测试
DI是一种抽象工厂
DI需要DI容器
延迟绑定
延迟绑定是一种不需要重新编译代码就能替换应用程序一部分的能力。
DI能提供延迟绑定,但两者不能划等号。
一种抽象工厂
很多人将DI看成一种服务,用于装载别的服务,也就是服务装载着(Service Locator)。但这是DI的对立面。
DI容器
DI容器是一个可选库,方便我们组合组件,但这不是必须的。
DI的目标
替换原则,DI最重要的软件设计原则。
在软件设计中,实现同一接口的一个实现拦截另一个实现,这个叫做装饰者模式。它能使我们引进新的特性和方面而不用重写原有的代码。
另一种给现有代码增加新功能的方式是对接口的当前实现用一个新的实现替代。组合模式?
还有一种就是用到了适配器模式。
使用DI的好处
延迟绑定
到运行时才确定类型。
可扩展性
使用装饰模式来增加可扩展性。比如有一个Interface IWriteMessage,已经有一个ConsoleWriteMessage实现了这个接口,在Console中打印消息,现在需要增加一个Authentication,那么可以另外定义一个类AuthenticationWriteMessage类,它也实现IWriteMessage接口,同时它的构造函数接受一个IWriteMessage类型,在接口的方法中增加要扩展的功能,然后调用构造函数接受的类型的方法。
这个实现了开闭原则。 COMPOSITION ROOT
同时,ConsoleWriteMessage类实现最终功能,AuthenticationWriteMessage类实现验证,分离关注点。
并行开发
分离关注点后,功能可以交给不同的开发团队并行开发。
可维护性
每个类的职能清晰定义并受到限制,维护就变的简单多了。
这是单一职能原则。
可测试性(指单元测试)
因为遵守了依赖倒置原则,所以单元测试很容易实现。
Test Doubles
什么该注入,什么不该注入
.NET Base Library定义了很多类型,我们引用一个程序集,然后使用其中的类型。这样是不是就是增加了耦合度?
把依赖分成稳定依赖(Stable Dependency)和可变依赖(Volatile Dependency)很有帮助。
稳定依赖
BCL的很多模块都不会增加应用程序的耦合度。它们提供了可重用的功能来使你的代码简洁。
默认的,你可以把几乎所有定义在BCL中的类型都认为是稳定依赖。
稳定依赖的评判标准
类或模块已经存在
你预期新版本不会有破坏性的改变
算法是固定的
你不会考虑去替换这个类或模块
可变依赖
判断标准
依赖引入了这样一个需求,需要为程序创建和配置运行时环境。关系型数据库就是一个很好的例子。这种类型缺少延迟绑定和可扩展性,使得可测试性困难。
依赖并不存在,但是它存在于开发中。一个明显的症状就是不能并行开发。
依赖不能安装在开发组织的所有机器上。比如昂贵的第三方组件。
依赖包含不确定性的行为。
DI的边界
将创建依赖的职责从类中移出,虽然类不能对创建的过程再做控制,但是我们开发人员可以。而且我们可以统一创建依赖的方式。
对象组合、拦截和生命周期管理是DI的三个方面。
对象组合
为了获得可扩展性、延迟绑定和并行开发的好处,我们使用对象组合。刚开始,DI就等同于对象组合。
对象生命周期
使用依赖注入,对象放弃了创建依赖的权利,同时也放弃了控制依赖生命周期的权利。.NET的垃圾收集器会帮我们处理。
依赖可以为各个对象分别创建,也可以共享给各个对象。所以对象不需要控制依赖的生命周期。
如果依赖实现了IDispose接口,事情会变得比较复杂(第八章)。
拦截
当我们把对依赖的控制交给第三方,我们就有了在把依赖传给对象前对其进行修改的权利。也就是使用装饰模式。
通过使用拦截,我们得到了方面编程的能力。