C++中的依赖注入:完整指南与示例

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C++ 中的依赖注入简介



在现代 C++ 开发中,创建灵活且可测试代码的关键原则之一是 依赖注入(DI)。这种设计模式有助于降低系统组件之间的耦合度,使代码更加模块化、可重用,且更容易测试。



在本文中,我们将探讨什么是 C++ 中的依赖注入、它解决了哪些问题、研究在实际中实现它的各种方法,并提供代码示例来帮助你将该方法融入你的项目中。



直接依赖的问题



在讨论解决方案之前,我们先来看一个典型问题。假设我们有一个 UserService 类,它使用 Logger 进行日志记录:



class Logger {public:    void log(const std::string& message) {        std::cout << "[LOG]: " << message << std::endl;    }};

class UserService {private: Logger logger; // 硬依赖public: void createUser(const std::string& name) { logger.log("Creating user: " + name); // ... 用户创建逻辑 }};


这里,UserService 自行创建了一个 Logger 实例。这带来了几个问题:

  • 测试复杂性:在单元测试中无法用模拟对象替换日志记录器。
  • 紧耦合:更改 Logger 构造函数需要修改所有使用它的类。
  • 违反单一职责原则(SRP):该类不仅负责自身逻辑,还负责创建其依赖项。


什么是依赖注入?



依赖注入 是一种模式,其中对象从外部接收其依赖项,而不是自行创建。核心思想是:"不要请求,而是接收。"



在 C++ 中,主要有三种注入依赖项的方式:

  • 构造函数注入 — 通过构造函数。
  • 设值方法注入 — 通过设值方法。
  • 接口注入 — 通过接口。


1. 构造函数注入



这是最常见且推荐的方法。依赖项通过类的构造函数传入:



#include <memory>#include <iostream>

class ILogger {public: virtual ~ILogger() = default; virtual void log(const std::string& message) = 0;};

class ConsoleLogger : public ILogger {public: void log(const std::string& message) override { std::cout << "[CONSOLE]: " << message << std::endl; }};

class FileLogger : public ILogger {public: void log(const std::string& message) override { // 文件写入逻辑 std::cout << "[FILE]: " << message << std::endl; }};

class UserService {private: std::unique_ptr<ILogger> logger_; // 通过接口的依赖public: // 构造函数注入 explicit UserService(std::unique_ptr<ILogger> logger) : logger_(std::move(logger)) {}

void createUser(const std::string& name) { logger_->log("Creating user: " + name); // ... 逻辑 }};

int main() { auto consoleLogger = std::make_unique<ConsoleLogger>(); UserService service(std::move(consoleLogger)); service.createUser("Alice"); return 0;}


优点:

  • 依赖项在构造函数签名中清晰可见。
  • 对象在创建后始终处于有效状态。
  • 易于测试——你可以替

博客

书籍推荐