产品战略专家梁宁确认出席AICon北京站,分享AI时代下的商业逻辑与产品需求 了解详情
写点什么

成功实现依赖注入

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

评论

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

建设智慧公厕有什么好处?都有哪些功能?

光明源智慧厕所

今日分享丨单点登录原理及OAuth20授权码协议

inBuilder低代码平台

低代码 单点登录

Mistral Large模型现已在Amazon Bedrock上正式可用

财见

解析名企测试流程:从项目立项到产品上线的完整指南

测吧(北京)科技有限公司

测试

深度探索名企项目开发:揭秘经典开发流程与测试策略

测吧(北京)科技有限公司

测试

企业架构设计原则之品质均衡性(一)

凌晞

企业架构 架构设计 架构设计原则

我们是如何测试人工智能的(三)数据构造与性能测试篇

测吧(北京)科技有限公司

测试

Apache IoTDB 入选国家级规划教材《数据库系统概论(第6版)》!

Apache IoTDB

Digital Realty 将人工智能驱动的能效平台扩展至亚太地区

财见

精通Linux性能优化:掌握CPU、内存、网络和IO性能调优的技巧与工具

测吧(北京)科技有限公司

测试

Rust 解码 Protobuf 数据比 Go 慢五倍?记一次性能调优之旅

Greptime 格睿科技

Go rust 性能 序列化 企业号 4 月 PK 榜

零信任安全模型:构建未来数字世界的安全基石

GousterCloud

零信任

我们是如何测试人工智能的(六)推荐系统拆解

测吧(北京)科技有限公司

测试

探秘Linux进程与线程:多进程与多线程的奥秘及实战场景

测吧(北京)科技有限公司

测试

ERC314协议代币开发及合约开发详解

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发 公链开发

揭秘Linux进程通讯:解决死锁难题的方法论

测吧(北京)科技有限公司

测试

我们是如何测试人工智能的(七)包含大模型的企业级智能客服系统拆解与测试方法 – 知识引擎

测试人

人工智能 软件测试 自动化测试 测试开发

我们是如何测试人工智能的(七)包含大模型的企业级智能客服系统拆解与测试方法 – 知识引擎

测吧(北京)科技有限公司

测试

我们是如何测试人工智能的(八)包含大模型的企业级智能客服系统拆解与测试方法 – 大模型 RAG

测吧(北京)科技有限公司

测试

ChatGPT全方位解析:如何培养 AI 智能对话技能?

测吧(北京)科技有限公司

测试

我们是如何测试人工智能的(二)数据挖掘篇

测吧(北京)科技有限公司

测试

精通测试规划:打造完备的测试计划与总结报告

测吧(北京)科技有限公司

测试

高效管理测试资源:工具化管理测试用例与Bug漏洞

测吧(北京)科技有限公司

测试

广东智慧公厕管理系统哪家好

光明源智慧厕所

淘宝商品评论API:连接消费者与商家的桥梁,提升购物体验新途径

技术冰糖葫芦

API 文档

云原生数据库下一站:像 MySQL 一样流行,让更多人受益于新技术的发展

百度Geek说

云计算 云原生数据库

我们是如何测试人工智能的(四)补充:模型全生命周期流程与测试图

测吧(北京)科技有限公司

测试

2024年智慧厕所解决方案,光明源智能科技是怎么实现的。

光明源智慧厕所

我们是如何测试人工智能的(五)案例介绍:ASR 效果测试介绍

测吧(北京)科技有限公司

测试

性能测试中的唯一标识问题研究

FunTester

测试管理实战:优化测试流程,提升项目质量与效率

测吧(北京)科技有限公司

测试

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