当前位置: 首页 > news >正文

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

依赖注入 (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; }
http://www.jsqmd.com/news/500139/

相关文章:

  • 体验完阿里「悟空」,我想把电脑里的龙虾换掉了,是真NB!
  • 基于SpringBoot的汽车美容保养系统
  • 主机管理---windows2012配置ftp服务器20240813
  • Ansys Zemax | 什么是Sobol取样?
  • 词嵌入(Word Embedding)和位置编码(Positional Encoding)
  • 常用的AIGC 检测工具有哪几种?
  • 被查出AI率不要慌!2026免费毕业论文去痕神器盘点
  • Cesium 中基于 1.19.11 实现自定义影像与哈密地形加载
  • 素材分类即搜即用:视频数字资产管理让制作周期缩短 70%,效率翻倍
  • [Win11家庭中文版]如何关闭基于虚拟化的安全性VBS(为了解决VBS启用状态下 VMware性能很差 频繁闪退或有各种不一样的崩溃报错)
  • 【小白说】【论文拆解】Neural-Pull: Learning Signed Distance Functions from Point Clouds by Learning to Pull Sp
  • Window(10/11)QQ多开
  • 嘎嘎降AI9大平台验证怎么用?上传到出结果完整操作录屏
  • SEO_2024年最新的SEO策略与方法详解
  • SEO_持续提升网站SEO排名的长期维护方案
  • shell脚本语言知识点总结
  • PDF转Word工具2026深度评测:5款主流工具权威评分体系与案例解析
  • 跨境电商合规智能化:版权检测能力与 Agent Skill 的技术融合与落地实现
  • 刚刚!GPT-5.4 mini/nano正式发布,轻量编程模型性能逼近满血版
  • 2026盘点:毕业论文AIGC降重怎么破?这款工具免费用!
  • SEO_中小企业必备的低成本SEO推广方法
  • 二十、kubernetes基础-30-kubernetes-ha-binary-deployment-07-dns-operations
  • 什么是Skill
  • 3月18日打卡
  • 告别AI Agent学习焦虑!6个GitHub项目带你从入门到精通,附可执行路线图
  • RPC核心原理:组件与调用流程
  • 2025年10款降AI率工具深度测评:论文降ai率,谁是真能手?
  • 智破纸质壁垒 赋能医药合规——旗讯数字医药注册批件纸质文档智能识别与结构化提取对接解决方案
  • 2026年本科毕业论文查AI率用什么工具预检?这3个又快又准
  • 消息中间件RabbitMQ04:路由模式+死信队列的应用实践模板