news 2026/8/2 12:14:21

【设计模式】依赖注入控制反转

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【设计模式】依赖注入控制反转

依赖注入 (Dependency Injection, DI) 作为一种设计模式,其核心思想是通用的,可以应用于多种编程语言和环境。

虽然“依赖注入”这个术语在某些框架(如 Spring for Java, ASP.NET Core for C#)中被高度形式化和自动化,但它本质上是一个更广泛的、语言无关的设计原则,用于实现控制反转 (Inversion of Control, IoC)

依赖注入的核心思想 (Language-Agnostic)

  1. 解耦:一个类(我们称之为“客户端”或“服务消费者”)不应该自己创建它所依赖的其他类(“服务提供者”)的实例。这样会导致硬编码的依赖关系,使得代码难以测试和维护。
  2. 外部注入:服务提供者的实例应该由外部实体(比如框架、工厂,甚至是另一个类)创建,并“注入”到客户端中。
  3. 面向接口:客户端依赖于服务的抽象(如接口或抽象类),而不是具体的实现。这样,注入不同的实现就变得非常容易。

控制反转 (Inversion of Control, IoC)

定义: 将对象创建和管理的控制权从代码本身交给外部容器。

“控制反转”是一种设计原则,它颠覆了传统程序的控制流。在没有 IoC 的传统代码中,一个对象(我们称之为 Client)需要另一个对象(Service)来完成工作,它会主动去创建或查找这个 Service 的实例

// 传统方式:控制权在 Client 手中 class Client { private: Service* service_; // Client 自己创建依赖 public: Client() { service_ = new Service(); // 硬编码,高耦合 } void doWork() { service_->performAction(); } };

“控制反转”将这种创建和管理依赖的控制权Client反转给了一个外部的“第三方”——通常是框架或容器。

// 控制反转:控制权交给外部 class Client { private: Service* service_; // 依赖由外部提供 public: // 依赖由外部注入进来 Client(Service* svc) : service_(svc) {} void doWork() { service_->performAction(); } }; // 在 main 或某个配置类中,由“外部”来决定注入哪个实现 int main() { Service* realService = new RealService(); Client client(realService); // 控制权在这里,由外部注入 client.doWork(); }

控制反转 VS 依赖注入:

这两个概念常常被混淆,它们的关系如下:

  • 控制反转 (IoC)是目的,是一个宏观的设计思想和原则。
  • 依赖注入 (DI)是实现这个目的的一种具体手段和技术。

可以通过依赖注入来实现控制反转,但理论上也可能有其他的实现方式(尽管 DI 是最主流和最实用的方式)。

主要优势:

  1. 降低耦合性 (Decoupling):Client不再依赖于Service的具体实现,而是依赖于抽象(如接口)。这使得ClientService的具体实现类解耦,更容易替换和升级。
  2. 提高可测试性 (Testability):在单元测试中,你可以轻松地将一个真实的Service替换为一个模拟的MockService,来隔离地测试Client的逻辑。
  3. 提高可维护性 (Maintainability):由于依赖关系是通过外部配置或注入来管理的,修改依赖关系通常不需要修改Client的代码,只需要修改注入的配置即可。

实现方式:

  • 构造函数注入 (Constructor Injection):通过构造函数将依赖传递给对象。这是最常用和推荐的方式。
  • Setter 注入 (Setter Injection):通过对象的 Setter 方法注入依赖。
  • 接口注入 (Interface Injection):通过实现一个特定的接口来注入依赖,这种方式较为少见。

应用场景:

  • 框架: Spring (Java), ASP.NET Core (C#) 等现代框架的核心就是基于 IoC 容器,它管理着应用程序中几乎所有对象的生命周期和依赖关系。
  • 大型项目: 在复杂的软件系统中,IoC 是管理庞大对象网络的有效方法。

比如fastDDS中的一段代码,其实现的设计模式是IoC,而没有使用依赖注入

auto ret = EXIT_SUCCESS; CLIParser::benchmark_config config = CLIParser::parse_cli_options(argc, argv); uint32_t timeout = 1; switch (config.entity) { case CLIParser::EntityKind::PUBLISHER: timeout = config.pub_config.timeout; break; case CLIParser::EntityKind::SUBSCRIBER: timeout = 0; break; default: break; } std::string app_name = CLIParser::parse_entity_kind(config.entity); std::shared_ptr<Application> app; try { app = Application::make_app(config); } catch (const std::runtime_error& e) { EPROSIMA_LOG_ERROR(app_name, e.what()); ret = EXIT_FAILURE; }

为什么说实现了“控制反转”?

控制反转 (Inversion of Control) 的核心在于 “将创建对象的控制权从使用者手中反转给外部”。

  • 传统方式:main函数会自己编写代码,手动创建ParserConfigApplication以及Application内部的所有组件(如Publisher,Subscriber)。
  • 这段代码的方式:main函数只负责解析命令行参数 (parse_cli_options),然后将这个配置 (config) 交给Application::make_app这个外部实体(工厂方法) 来创建Application对象。main不再关心Application内部是如何被构建的。控制权从main反转到了make_app方法。

为什么说没有实现“依赖注入”?

依赖注入 (Dependency Injection) 是实现控制反转的一种具体技术手段,它的核心在于 “对象的依赖是由外部提供(注入)给它的,而不是由它自己创建”。

  • 在这段代码中:Application::make_app方法内部,很可能包含了创建其所有内部组件(如Publisher,Subscriber,NetworkTransport等)的逻辑。Application类本身并没有暴露一个接口(如构造函数参数或 setter 方法)来让外部将这些依赖“注入”进来。Application仍然是自己在内部创建和管理它的依赖。

类比

我们可以用一个生活中的例子来类比:

  • main函数: 一个顾客。
  • Application::make_app: 一家餐厅的厨师。
  • Application: 一道菜。
  • 传统方式 (无 IoC):顾客自己买菜、洗菜、切菜、下厨做菜。
  • 这段代码 (IoC, 无 DI):顾客告诉厨师(make_app)想要什么口味的菜(config),然后由厨师(make_app)完成所有工作,包括采购食材、烹饪。顾客不参与制作过程(控制权反转)。但厨师(make_app)是自己去买菜(创建依赖),而不是由顾客或其他人把菜(依赖)准备好递给厨师(注入)。
  • 依赖注入 (DI):顾客把所有准备好的食材(各种依赖对象)都交给厨师(Application),厨师只负责烹饪(使用这些依赖)。这才是“注入”。

改成依赖注入版本如下

// 假设的依赖注入版本 try { // 外部(例如 main 或一个专门的 IoC 容器)负责创建所有依赖 auto publisher = std::make_shared<Publisher>(config.pub_config); auto subscriber = std::make_shared<Subscriber>(config.sub_config); // 然后将这些依赖注入到 Application 的构造函数中 app = std::make_shared<Application>(publisher, subscriber, config); } catch (const std::runtime_error& e) { EPROSIMA_LOG_ERROR(app_name, e.what()); ret = EXIT_FAILURE; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 15:03:10

分支与循环

分支if 语句语法形式&#xff1a;表达式为真&#xff0c;执行语句&#xff0c;为假则不执行if &#xff08;表达式&#xff09;语句;if else语句语法形式&#xff1a;表达式为真&#xff0c;执行语句1&#xff0c;否则执行语句2if &#xff08;表达式&#xff09;语句1;else …

作者头像 李华
网站建设 2026/7/14 15:03:12

拖延症福音!全领域适配的降AI率工具 —— 千笔·专业降AIGC智能体

在AI技术深度渗透学术写作的今天&#xff0c;越来越多的学生、研究人员和职场人士开始依赖AI工具提升写作效率。然而&#xff0c;随着查重系统对AI生成内容的识别能力不断提升&#xff0c;论文中的“AI率超标”问题逐渐成为困扰众多作者的难题。无论是知网、维普还是Turnitin&a…

作者头像 李华
网站建设 2026/7/14 15:03:14

查重率 30% 降至 3%:别再用 Gemini 3 玩“改词换句”的低级游戏了!

01. 这种“改了白改”的绝望&#xff0c;你一定懂深夜一点&#xff0c;当你把精心打磨的初稿传上查重系统&#xff0c;几分钟后跳出的那一抹刺眼的“深红色”&#xff0c;足以让任何科研人瞬间破防。最让人绝望的是&#xff0c;你明明已经对着那些红字&#xff0c;把“因为”改…

作者头像 李华
网站建设 2026/7/14 15:03:29

SEO_详解SEO关键词研究的完整步骤与工具推荐

SEO关键词研究的完整步骤与工具推荐在当今竞争激烈的互联网环境中&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;是提升网站排名和吸引流量的关键。而在SEO的众多策略中&#xff0c;关键词研究占据了核心位置。本文将详细解析SEO关键词研究的完整步骤与推荐的工具&#…

作者头像 李华
网站建设 2026/7/14 15:03:30

多线程 -Thread

一.概念一个进程太“重”了&#xff0c;创建一个进程&#xff0c;比较重&#xff08;消耗的资源比较多&#xff0c;时间也比较长&#xff09;&#xff0c;销毁一个进程也很重所以线程就是为了解决这个问题&#xff0c;线程要比进程“轻”创建线程的开销比创建进程小很多&#x…

作者头像 李华
网站建设 2026/7/14 15:03:27

2026年国内AI镜像站选择指南:站长、开发者、内容创作者的实战推荐

面对市面上众多的AI镜像站&#xff0c;国内用户如何选择真正稳定、性价比高的服务平台&#xff1f;本文将从实际使用场景出发&#xff0c;为站长、开发者、内容创作者三大群体提供针对性的选择建议和实战推荐。 目前国内用户想免费使用Gemini 3.1 Pro&#xff0c;最推荐的是聚…

作者头像 李华