依赖倒置要求高层模块依赖抽象接口而非具体实现,如OrderService应通过IDatabase&构造而非new DatabaseMySQL;必须配合依赖注入,且禁止在高层模块中出现任何具体类名、头文件包含或硬编码创建逻辑。

普通对象依赖就是直接 new 具体类
比如 OrderService 里直接写 new DatabaseMySQL(),或者把 DatabaseMySQL* 当作成员变量初始化。这种写法会让高层模块(业务逻辑)和低层模块(数据库实现)在编译期就绑死:改数据库就得改 OrderService 源码,加个日志、换 PostgreSQL、Mock 测试都得动它。
本质是「细节控制抽象」——具体类决定了怎么用,上层只能将就;而依赖倒置要求「抽象控制细节」,接口定义行为契约,具体类只负责履约。
- 普通依赖:头文件里
#include "DatabaseMySQL.h",链接时强绑定 - 依赖倒置:头文件只含
#include "IDatabase.h",OrderService构造函数参数是IDatabase&或IDatabase* - 编译时,
OrderService.o不知道也不关心背后是 MySQL 还是 SQLite
依赖倒置必须靠抽象类或纯虚接口承载
没有 class IDatabase { public: virtual ~IDatabase() = default; virtual bool save(const Order&) = 0; }; 这种接口,就谈不上倒置。C++ 里抽象接口不是语法糖,它是编译器强制的契约:派生类必须实现所有纯虚函数,否则无法实例化。
普通依赖可以绕过接口,直接传 DatabaseMySQL 对象;依赖倒置则禁止这么做——OrderService 的构造函数签名里不能出现任何具体类名,否则违反原则。
立即学习“C++免费学习笔记(深入)”;
- 允许:
OrderService(IDatabase& db) - 禁止:
OrderService(DatabaseMySQL& db)或OrderService(std::unique_ptr<databasemysql> db)</databasemysql> - 工厂类(如
DatabaseFactory::create())可以返回具体类,但它的调用点必须在 main 或 DI 容器里,不能藏在OrderService内部
依赖方向在源码层面被反转了
普通依赖中,OrderService 的 .cpp 文件里 #include "DatabaseMySQL.h",意味着 OrderService 依赖 DatabaseMySQL;而依赖倒置后,DatabaseMySQL.cpp 反而要 #include "IDatabase.h",并继承它——依赖关系从「上层指向下层」变成「下层向上层接口靠拢」。
这个反转不是语义游戏,它让 IDE 能正确识别「哪些类实现了 IDatabase」,也让链接器能动态替换实现(比如测试时用 MockDatabase 替代真实 DB),而无需重新编译 OrderService。
- 普通依赖:修改
DatabaseMySQL→ 可能触发OrderService.o重编译 - 依赖倒置:新增
DatabasePostgreSQL→ 只需改 main 函数里的创建逻辑,OrderService.o完全不用碰 - 关键信号:如果某个类的头文件里出现了非标准库的具体实现类名,大概率没做到依赖倒置
不配合依赖注入,依赖倒置就只剩一半力气
只定义了 IDatabase 接口,但在 OrderService 构造函数里还是 db_(new DatabaseMySQL()),那只是「披着抽象外衣的硬编码」。真正的倒置需要把具体实现的创建权交出去——通常通过构造函数参数、setter 方法,或由外部容器(如自己写的简易 DI 类)注入。
否则,OrderService 仍承担了「知道哪里找实现」的责任,它和 DatabaseMySQL 之间依然存在隐式耦合(比如依赖其头文件路径、链接选项、构造参数等)。
- 有效注入:
auto db = std::make_unique<databasemysql>("localhost"); OrderService svc(*db);</databasemysql> - 无效「假注入」:
OrderService() : db_(std::make_unique<databasemysql>("localhost")) {}</databasemysql>—— 实现细节仍锁死在类内部 - 容易忽略的点:
std::unique_ptr<idatabase></idatabase>是安全的,但std::unique_ptr<databasemysql></databasemysql>就破坏了抽象边界
new、一个头文件包含、一个类型别名定义。


















