速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

成功实现依赖注入

  • 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:019269

评论

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

C#/VB.NET 如何在 Word 文档中添加页眉和页脚

在下毛毛雨

C# .net word文档 页眉页脚

存储拆分后,如何解决唯一主键问题?

小小怪下士

Java 程序员 后端 uuid

Java实现Http多次请求复用同一连接

做梦都在改BUG

Java HTTP

开心档之Bootstrap4 自定义表单

雪奈椰子

bootstrap 开心档

SpringBoot与Loki的那些事

做梦都在改BUG

Java Spring Boot 框架

设计原则 — L 里氏替换原则

Lemoon Can

SOLID 设计原则 SOLID原则 里氏替换原则

基于图数据库 NebulaGraph 实现的欺诈检测方案及代码示例

NebulaGraph

图数据库 反欺诈 安全风控

【我和openGauss的故事】openGauss的WDR报告解读

openGauss

openGauss内核分析(四):查询重写

openGauss

Cadence Allegro如何通过Excel表格创建元器件?

华秋PCB

科普 硬件 元器件

【漏洞发现】|多个严重CVE漏洞被发现,系内存类安全漏洞

云起无垠

漏洞 Fuzzing

国内开源生态发展现状:开源基金会与开源组织 | 雨林开源行

开源雨林

Linux 开源 基金会 OSPO

阿里云 EMAS & 魔笔 :1月产品动态

移动研发平台EMAS

阿里云 App 低代码 移动研发

万物皆可集成资源包!低代码集成系列一网打尽

葡萄城技术团队

【ECCV 2022】TeSTRa:稳定的流式视频识别

Zilliz

计算机视觉

ChatGPT被开发者嫌弃?真正的用户群体出现

Onegun

人工智能 ChatGPT

兴业银行正式加入openGauss社区

openGauss

openGauss社区十一月运作报告

openGauss

实用指南:如何在Anolis OS上轻松使用 Kata 安全容器?

OpenAnolis小助手

容器 安全 操作系统 龙蜥社区 kata

提升企业管理效率 从权限控制开始

力软低代码开发平台

云小课|MRS基础原理之Flink组件介绍

华为云开发者联盟

大数据 华为云 企业号 2 月 PK 榜 华为云开发者联盟

一文详解SpEL表达式注入漏洞

华为云开发者联盟

开发 华为云 企业号 2 月 PK 榜 华为云开发者联盟

要想随时编码即刻创新,这个工具你需要一个

华为云开发者联盟

云计算 后端 华为云 企业号 2 月 PK 榜 华为云开发者联盟

数仓专家面对面 | 为什么我选择GaussDB(DWS)

华为云开发者联盟

数据库 后端 华为云 企业号 2 月 PK 榜 华为云开发者联盟

GitHub神坛变动!10W字Spring Cloud Alibaba笔记,30W星标登顶第一

做梦都在改BUG

Java 微服务 Spring Cloud

AI不仅造就了ChatGPT,也重新定义了模糊测试

云起无垠

模糊测试

如何使用 NFTScan NFT API 检索单个 NFT 资产

NFT Research

API NFT

赛意信息荣获2022年深信服优秀ISV合作伙伴奖 强强联手赋能企业再升级

科技热闻

横空出世!IDEA 版 API 接口神器来了,一键生成文档!

Liam

Java 后端 IDEA Java 分布式 API文档

从混沌到清晰,阿里全球商品类目域建设思考

阿里技术

全球化技术能力

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