写点什么

成功实现依赖注入

  • 2011-12-29
  • 本文字数:2350 字

    阅读完需:约 8 分钟

我在写《Dependency Injection in .NET》时经常碰到的一个反应是“你怎么把依赖注入写成一整本书?”这种难以置信的反应是很自然的,如果你觉得依赖注入的主要模式(构造函数注入)非常容易理解。

虽然这个主要模式易于理解,却很难成功实现依赖注入,因为这个机制只是一个更大的上下文里的一部分。DI 是对控制反转(IoC)原则的应用,想要成功实现IoC,你就要把你的思维逆转过来。这篇文章勾画了成功实现DI 所需的心智模型。

松散耦合:依赖注入 vs. 服务定位器

如果你不理解DI 的目的,就很容易把它实现错。这是我最近看到的一个例子:

private readonly ILog log;

public MyConsumer(ILog log)
{
this.log = log ?? LogManager.GetLogger(“My”);
}

从封装的角度来看,这个方案的主要问题是 MyConsumer 类好像无法确定它是否对日志程序这个依赖的创建拥有控制权。虽然这个示例很简单,但如果 LogManager 返回的 ILog 实例包装了一个非托管资源,需要在不使用的时候释放掉,那就有可能演变成一个问题。

这样的实现之所以会出现,是因为开发者把全部精力放在让 MyConsumer 可以进行单元测试。这样做的理由是开发者只想在单元测试的时候可以替换 ILog,其它情况应该使用 LogManager 返回的实例。

这实际上就是 Bastard Injection 反模式。其中一个潜在问题是它很容易违反里氏代换原则,因为某个特定的实现得到了特殊对待。

DI(Dependency Injection,依赖注入)的目的比起单纯地协助进行单元测试要广泛的多。它的目的是实现松散耦合,以便提升整个解决方案的可维护性。(如果你想知道为什么松散耦合能够增加可维护性,我的书的第一章讨论了这个话题,你可以免费下载试读。)

松散耦合可被概述为基于接口而不是具体实现进行编程的思想。但是,因为接口没有构造函数,如何创建那些接口的实例马上就成了一个问题。

根据你的编程方式,有两种完全不同的方案可以获取接口的实例:

对于前面的示例,很容易就会演变成服务定位器(Service Locator)反模式,像这样:

复制代码
<span color="#0000ff">public</span> MyConsumer()
{
    <span color="#0000ff">this</span>.log = <span color="#0080ff">Locator</span>.Resolve<<span color="#0080ff">ILog</span>>();
}

除了服务定位器的其它问题,这种方案的问题还在于 LogManager.GetLogger(“My”) 方法调用所需的参数丢失了。假设其它 consumer 对象需要的日志程序是通过不同的参数实例化的,那么这个版本的服务定位器就无法工作了。

这通常会导致定位器的 Resolve 方法有一个或多个重载版本,以便向服务定位器提供上下文信息。这样,离违反里氏代换原则就不远了。

DI 提供了一个更好的方案:

复制代码
<span color="#0000ff">private readonly</span> <span color="#0080ff">ILog</span> log;
<p><span color="#0000ff">public</span> MyConsumer(<span color="#0080ff">ILog</span> log)<br></br>{<br></br>    <span color="#0000ff">if</span> (log == <span color="#0000ff">null</span>)<br></br>        <span color="#0000ff">throw new</span> <span color="#0080ff">ArgumentNullException</span>(<span color="#c0504d">"log"</span>);<br></br>    <span color="#0000ff">this</span>.log = log;<br></br>}</p>

这是控制反转的纯粹形式。ILog 的任何实现都能接受,同时通过条件语句保证这个实例不为 null。这跟使用服务定位器相反,上下文没有在构建对象图的时候丢失。

复制代码
<span color="#0000ff">var</span> consumer = <span color="#0000ff">new</span> <span color="#0080ff">MyConsumer</span>(
    <span color="#0080ff">LogManager</span>.GetLogger(<span color="#c0504d">"My"</span>));

在创建 MyConsumer 的实例时,负责创建的代码知道这个特定的 consumer 对象使用哪个实现 ILog 接口的类,因此可以根据上下文提供正确的实现。

DI 和服务定位器是实现松散耦合的两种互斥方案。技术上,两种都是可行的,但 DI 没有服务定位器的缺点。

DI 的唯一缺点是它不像服务定位器那样易于理解。想要成功实现 DI,你需要克服一些障碍。

通往依赖注入的崎岖之路

学习 DI 的其中一个挑战,也是你首先碰到的最难的问题:如何获取一个接口的实例?好消息是一旦你理解构造函数注入只是简单地通过构造函数请求一个实例,你就跨越了这个最艰难的障碍。

接下来的挑战比较容易解决,往后一个更加容易。我喜欢把这些挑战想象成你需要攀越的山。第一个又高又陡,但下一个会比较容易,从那之后很快就会变得平坦:

在成功实现 DI 的路上,你的第一个障碍是理解通过构造函数注入把构建 consumer 对象及其依赖的责任委托给第三方。这个第三方就是对象组合的根(Composition Root),它是应用程序里的一个独立的点,整个对象图都是在这里构建的。因为对象组合的根负责构建整个对象图,所以它掌握了整个上下文,这使它可以对谁依赖谁这个问题做出明智的决定。

这可能吓跑了一些开发者,因为他们害怕这会导致性能问题,但事实并非如此

对于大多数人来说,第二个障碍可能是某个依赖的确定需要一个运行时的值。这种情况通常发生在某个依赖要等用户在用户界面上做出特定的选择时才能确定。对于这种情况,抽象工厂通常是一个解决方案

根据我的经验,最先的两个障碍是最难克服的。其它挑战也会出现,但一般都是个别现象。在我的书的第六章里,我收集了人们可能碰到的常见问题,以及解决它们的办法。

不管怎样,一旦你对DI 形成了正确的心智模型,任何挑战都能轻易解决的。

关于作者

Mark Seemann AutoFixture 的创作者,也是《Dependency Injection in .NET》的作者。他是一个专业的软件开发者和架构师,他住在丹麦的哥本哈根,目前是一家丹麦咨询公司Commentor 的软件架构师。他喜欢阅读、画画、弹吉他、好酒以及美食。

查看英文原文: Succeeding with Dependency Injection

2011-12-29 09:019465

评论

发布
暂无评论
发现更多内容

博睿数据APM适配欧拉开源操作系统,为开发者性能体验保驾护航

博睿数据

WeTest与腾讯安全联合推出小程序质量方案,助力私域流量2.0新增长

WeTest

Python代码阅读(第57篇):倒序字符串

Felix

Python 编程 string 阅读代码 Python初学者

阿里内网疯狂传阅的“M8级”分布式架构笔记,GitHub刚上线就霸榜

Java 编程 程序员 架构 阿里

数字化学习分享+一场思维探索工作坊+引导回顾会+公开演讲

研发管理Jojo

数字化转型 敏捷教练 咨询

送给正在入行的小白:最全最有用的网络安全学习路线已经安排上了

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

终于有人把“元宇宙”说清楚了

CECBC

爱奇艺基于SpringCloud的韧性能力建设

爱奇艺技术产品团队

全捐了,华为将欧拉开源操作系统代码、品牌等相关资产捐赠!!!

BeeWorks

【福利】腾讯WeTest专有云,限时开放招募体验官

WeTest

模块三作业

panxiaochun

架构实战营

质量基础设施一站式云服务平台搭建,NQI一站式服务平台

电微13828808271

百度APP移动研发平台及DevOps实践

百度开发者中心

DevOps 最佳实践 方法论 移动端 百度app

通用排序框架在爱奇艺推荐的应用

爱奇艺技术产品团队

云开发CloudBase集成腾讯数字身份管控平台CIAM,快速实现账号管理

腾讯安全

网易云信携手“瑶台”,打造元宇宙商业化实践标杆案例

网易云信

人工智能 虚拟化 虚拟人 元宇宙

2021年10月券商App行情刷新及交易体验评测报告,兴业证券荣登榜首!

博睿数据

投资元宇宙,如何开启混合现实开发

龙智—DevSecOps解决方案

游戏开发 元宇宙 混合现实

CSS页面设计稿构思与实现(四)之自定义字体

Augus

CSS 11月日更

网易云音乐网络库跨平台化实践

网易云信

数据库 网络库 跨平台化

从开始到放弃:某高校电子校友卡开发笔记

CC同学

今天面了个腾讯拿 38K 出来的,让我见识到了基础的天花板

Java 程序员 JVM springboot MyBatis标签

ZGC在合合信息HBase平台中的实践

合合信息大数据团队

大数据 性能优化 ZGC HBase 合合信息

自定义View:如何实现手动拖动的图片控件

Changing Lin

11月日更

【得物技术】得物直播秒开体验提升

得物技术

体验 直播 视频 优化 卡顿

Prometheus HTTP API 查询(六)Prometheus Server 状态和 TSDB 状态

耳东@Erdong

Prometheus PromQL HTTP API 11月日更

元宇宙让我们实现“办公自由”?想要远程办公,保证员工效率和有效管理才是关键!

极狐GitLab

Gartner:对中国央行数字货币的创新见解

BeeWorks

区块链符号理论:符号方案和符号系统

CECBC

WorkPlus移动门户开启数字化智慧办公新模式

BeeWorks

不愧是GitHub上标星120K的Java手册,全程干货,只讲重点

收到请回复

Java 程序员 后端 面试技巧

成功实现依赖注入_.NET_Mark Seemann_InfoQ精选文章