1. 依赖倒置

依赖倒置原则(Dependency Inversion Principle, DIP)是 SOLID 原则中的一项,其核心思想是通过抽象解耦高层模块和低层模块,使二者都依赖于抽象而非具体实现

依赖反转/倒置的体现:传统依赖方向是高层模块直接调用低层模块,在源码级别上高层模块依赖低层细节模块。而 DIP 通过抽象反转这种依赖关系,使低层模块的实现在源码级别上依赖高层定义的抽象(视为高层模块的一部分)。

1.1 依赖倒置原则的核心

  1. 高层模块不直接依赖低层模块,二者都应依赖抽象(接口或抽象类,接口由高层模块定义,视为高层模块的一部分)。
  2. 抽象不依赖细节,细节(具体实现)应依赖抽象。

1.2 依赖倒置指导方针

  • 变量不可以持有具体类的引用——改用工厂,避免直接使用 new 持有具体类的引用(new 具体类的操作都封装到工厂中)
  • 不要让类派生自具体类——派生自抽象类或接口,这样就不依赖具体类了
  • 不要覆盖基类中已经实现的方法——如果这样,说明不是一个真正适合被继承的抽象

1.3 示例

场景

  • 高层模块 ReportGenerator 需要生成报告,依赖数据获取功能。
  • 低层模块 MySQLDatabaseSQLiteDatabase 提供具体的数据操作。

传统实现(未遵循 DIP)

// 低层模块:直接依赖具体实现
class MySQLDatabase {
public:
void connect() { /* MySQL 连接逻辑 */ }
std::string fetchData() { return "MySQL 数据"; }
}; // 高层模块直接依赖低层具体类
class ReportGenerator {
private:
MySQLDatabase db; // 直接依赖具体实现
public:
void generateReport() {
db.connect();
auto data = db.fetchData();
std::cout << "报告数据: " << data << std::endl;
}
};

问题ReportGenerator 直接依赖 MySQLDatabase,更换数据库(如改用 SQLite)需修改高层代码。

遵循 DIP 的实现

  1. 定义抽象接口
class Database {
public:
virtual ~Database() = default;
virtual void connect() = 0;
virtual std::string fetchData() = 0;
};
  1. 低层模块实现接口
class MySQLDatabase : public Database {
public:
void connect() override { /* MySQL 连接逻辑 */ }
std::string fetchData() override { return "MySQL 数据"; }
}; class SQLiteDatabase : public Database {
public:
void connect() override { /* SQLite 连接逻辑 */ }
std::string fetchData() override { return "SQLite 数据"; }
};
  1. 高层模块依赖抽象
class ReportGenerator {
private:
Database& db; // 依赖抽象接口
public:
ReportGenerator(Database& database) : db(database) {} // 依赖注入
void generateReport() {
db.connect();
auto data = db.fetchData();
std::cout << "报告数据: " << data << std::endl;
}
};
  1. 使用示例
int main() {
MySQLDatabase mysqlDb;
SQLiteDatabase sqliteDb; ReportGenerator report1(mysqlDb); // 使用 MySQL
report1.generateReport(); ReportGenerator report2(sqliteDb); // 使用 SQLite
report2.generateReport(); return 0;
}

1.4 依赖倒置优势

  • 解耦:高层模块不依赖低层具体实现,可灵活替换数据库(如新增 MongoDB 只需实现 Database 接口)。
  • 可维护性:修改低层代码(如优化 MySQLDatabase)不影响高层模块。
  • 可测试性:可通过 Mock 对象(实现 Database 接口)轻松测试 ReportGenerator

1.5 依赖倒置小结

依赖倒置原则通过抽象解耦模块,使依赖关系从“高层 → 低层”变为“高层 → 抽象 ← 低层”,从而提升系统的灵活性和可维护性。在 C++ 中,可通过抽象类(接口)和依赖注入(如构造函数传入接口指针/引用)实现这一原则。

2. 依赖注入 DI

依赖注入(Dependency Injection, DI)是一种将对象依赖关系的外部化技术,其核心思想是:对象不直接创建或管理自己的依赖,而是由外部(调用者或框架)提供依赖的实例。通过这种方式,代码的耦合度降低,灵活性和可测试性显著提高。

2.1 依赖注入的本质

  1. 控制反转(IoC)

    依赖注入是控制反转的一种实现方式。传统代码中,对象自己控制依赖的创建(如 new 一个具体类),而依赖注入将这一控制权交给外部,实现“依赖被注入到对象中”。
  2. 依赖抽象而非实现

    依赖注入通常结合接口或抽象类使用,确保对象依赖的是抽象,而非具体实现(符合依赖倒置原则)。

2.2 依赖注入的三种方式

1. 构造函数注入(最常用)

通过构造函数传递依赖,确保对象在创建时即具备完整依赖。

class NotificationService {
private:
MessageSender& sender; // 依赖抽象接口
public:
NotificationService(MessageSender& sender) : sender(sender) {} // 构造函数注入
void sendMessage(const std::string& msg) {
sender.send(msg);
}
};

2. 属性注入(Setter 注入)

通过公开的成员属性或 Setter 方法动态设置依赖。

class NotificationService {
public:
void setSender(MessageSender& sender) { // Setter 注入
this->sender = &sender;
}
private:
MessageSender* sender;
};

3. 方法注入

通过方法参数传递依赖,适用于临时或局部依赖。

class NotificationService {
public:
void sendMessage(MessageSender& sender, const std::string& msg) { // 方法注入
sender.send(msg);
}
};

2.3 为什么需要依赖注入?

1. 解耦与可维护性

  • 传统代码:对象内部直接创建依赖,导致紧耦合。

class UserService {

private:

MySQLDatabase db; // 直接依赖具体类

};


若需改用 `SQLiteDatabase`,必须修改 `UserService` 的代码。 - **依赖注入**:通过接口解耦,仅需注入不同实现。 ```cpp
class UserService {
private:
Database& db; // 依赖抽象
public:
UserService(Database& db) : db(db) {}
};

2. 可测试性

  • 依赖注入允许在测试时替换为 Mock 对象。

    class MockDatabase : public Database { /* 模拟实现 */ };
    
    TEST(UserServiceTest) {
    MockDatabase mockDb;
    UserService service(mockDb); // 注入 Mock 对象
    // 执行测试...
    }

3. 扩展性

  • 新增功能时,只需实现新依赖并注入,无需修改现有代码。

    class MongoDB : public Database { /* 新数据库实现 */ };
    
    MongoDB mongoDb;
    UserService service(mongoDb); // 直接注入新依赖

2.4 C++ 依赖注入的实践技巧

1. 使用智能指针管理生命周期

避免裸指针导致的内存泄漏,使用 std::shared_ptrstd::unique_ptr

cpp

class NotificationService {
private:
std::shared_ptr<MessageSender> sender; // 智能指针管理依赖
public:
NotificationService(std::shared_ptr<MessageSender> sender) : sender(sender) {}
};

2. 结合工厂模式

通过工厂类集中管理依赖的创建逻辑。

class SenderFactory {
public:
static std::shared_ptr<MessageSender> createSender(const std::string& type) {
if (type == "email") return std::make_shared<EmailSender>();
else return std::make_shared<SmsSender>();
}
}; // 使用工厂创建依赖
auto sender = SenderFactory::createSender("email");
NotificationService service(sender);

3. 依赖注入容器(IoC Container)

在复杂项目中,使用容器自动管理依赖关系(如 Boost.DI)。

#include <boost/di.hpp>
namespace di = boost::di; // 定义接口和实现
class Database { /* ... */ };
class MySQLDatabase : public Database { /* ... */ }; // 配置容器
auto injector = di::make_injector(
di::bind<Database>().to<MySQLDatabase>()
); // 自动注入依赖
class UserService {
public:
UserService(Database& db) { /* ... */ }
};
UserService service = injector.create<UserService>();

2.5 依赖注入的常见误区

  1. 依赖注入 ≠ 工厂模式

    工厂模式负责创建对象,而依赖注入负责传递对象。二者常结合使用,但目的不同。
  2. 依赖注入 ≠ 必须用框架

    即使不用框架(如 Boost.DI),通过构造函数或参数传递依赖,也能实现依赖注入。
  3. 过度注入问题

    若一个类需要注入过多依赖(如超过 4 个),可能设计存在问题,需考虑拆分职责。

2.6 依赖注入小结

  • 依赖注入的核心:将依赖的创建和绑定从对象内部转移到外部。

  • 核心价值:解耦、可测试、可扩展。

  • C++ 实现关键:

    • 通过接口抽象依赖。
    • 使用构造函数/智能指针传递依赖。
    • 结合工厂模式或 IoC 容器管理复杂依赖关系。

3. 控制反转 IoC

IoC(Inversion of Control,控制反转) 是一种软件设计原则,其核心思想是将程序流程的控制权从开发者转移给框架或容器,以降低代码的耦合度,提高模块化和可维护性。它是实现依赖倒置原则(DIP)的关键机制,也是现代框架(如 Spring、.NET Core)和依赖注入(DI)容器的基础。

3.1 控制反转 IoC vs. 依赖注入 DI

  • IoC(控制反转):广义的设计原则,表示控制权转移的范式。其本质是将程序流程的控制权从开发者转移到框架或容器
  • DI(依赖注入):IoC 的一种具体实现技术,通过外部传递依赖。

关系

  • 依赖注入是控制反转的实现方式之一。
  • 控制反转还可以通过模板方法、回调(关联:好莱坞原则)等方式实现。
  • 使用 IoC 容器(如 Boost.DI)自动管理复杂依赖关系。

4. 工厂模式

尽管依赖倒置和依赖注入都强调面向抽象编程,但在实际编码中仍需创建(new)具体底层组件(ConcreteClass)

工厂模式主要分为三种,严格来说包括 简单工厂模式工厂方法模式抽象工厂模式。以下是它们的核心区别、适用场景及 C++ 示例:

4.1 简单工厂模式(Simple Factory)

有时候简单工厂不被视为正式的设计模式,而是一个编程习惯。

核心思想

  • 通过一个工厂类,根据传入的参数决定创建哪种具体产品对象。
  • 不符合开闭原则(新增产品需修改工厂类逻辑)。

适用场景

  • 产品类型较少且创建逻辑简单。
  • 不需要频繁扩展新类型。

C++ 示例

// 抽象产品
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() = default;
}; // 具体产品
class Circle : public Shape {
public:
void draw() override { std::cout << "画一个圆形" << std::endl; }
}; class Square : public Shape {
public:
void draw() override { std::cout << "画一个正方形" << std::endl; }
}; // 简单工厂类
class ShapeFactory {
public:
static Shape* createShape(const std::string& type) {
if (type == "circle") return new Circle();
else if (type == "square") return new Square();
else return nullptr;
}
}; // 使用示例
int main() {
Shape* circle = ShapeFactory::createShape("circle");
circle->draw(); // 输出: 画一个圆形
delete circle;
return 0;
}

4.2 工厂方法模式(Factory Method)

核心思想

  • 定义一个创建对象的抽象方法,由子类决定实例化哪个类。
  • 符合开闭原则(新增产品只需新增子类工厂)。

适用场景

  • 产品类型可能频繁扩展。
  • 需要将对象创建延迟到子类。

C++ 示例

// 抽象产品
class Database {
public:
virtual void connect() = 0;
virtual ~Database() = default;
}; // 具体产品
class MySQL : public Database {
public:
void connect() override { std::cout << "连接到 MySQL" << std::endl; }
}; class PostgreSQL : public Database {
public:
void connect() override { std::cout << "连接到 PostgreSQL" << std::endl; }
}; // 抽象工厂
class DatabaseFactory {
public:
virtual Database* createDatabase() = 0;
virtual ~DatabaseFactory() = default;
}; // 具体工厂
class MySQLFactory : public DatabaseFactory {
public:
Database* createDatabase() override { return new MySQL(); }
}; class PostgreSQLFactory : public DatabaseFactory {
public:
Database* createDatabase() override { return new PostgreSQL(); }
}; // 使用示例
int main() {
DatabaseFactory* factory = new PostgreSQLFactory();
Database* db = factory->createDatabase();
db->connect(); // 输出: 连接到 PostgreSQL
delete db;
delete factory;
return 0;
}

4.3 抽象工厂模式(Abstract Factory)

核心思想

  • 提供一个接口,用于创建相关或依赖对象族,而无需指定具体类。
  • 抽象工厂包含多个工厂方法,每个方法负责创建一个产品族中的对象。

适用场景

  • 需要创建一组相关或依赖的对象(例如 GUI 组件:按钮、文本框、下拉菜单等)。
  • 系统需要独立于产品的创建、组合和表示。

C++ 示例

// 抽象产品:按钮
class Button {
public:
virtual void render() = 0;
virtual ~Button() = default;
}; // 具体产品:Windows 按钮
class WindowsButton : public Button {
public:
void render() override { std::cout << "Windows 风格按钮" << std::endl; }
}; // 具体产品:MacOS 按钮
class MacOSButton : public Button {
public:
void render() override { std::cout << "MacOS 风格按钮" << std::endl; }
}; // 抽象产品:文本框
class TextBox {
public:
virtual void display() = 0;
virtual ~TextBox() = default;
}; // 具体产品:Windows 文本框
class WindowsTextBox : public TextBox {
public:
void display() override { std::cout << "Windows 风格文本框" << std::endl; }
}; // 具体产品:MacOS 文本框
class MacOSTextBox : public TextBox {
public:
void display() override { std::cout << "MacOS 风格文本框" << std::endl; }
}; // 抽象工厂
class GUIFactory {
public:
virtual Button* createButton() = 0;
virtual TextBox* createTextBox() = 0;
virtual ~GUIFactory() = default;
}; // 具体工厂:Windows 风格组件
class WindowsFactory : public GUIFactory {
public:
Button* createButton() override { return new WindowsButton(); }
TextBox* createTextBox() override { return new WindowsTextBox(); }
}; // 具体工厂:MacOS 风格组件
class MacOSFactory : public GUIFactory {
public:
Button* createButton() override { return new MacOSButton(); }
TextBox* createTextBox() override { return new MacOSTextBox(); }
}; // 使用示例
int main() {
GUIFactory* factory = new MacOSFactory(); Button* button = factory->createButton();
button->render(); // 输出: MacOS 风格按钮 TextBox* textBox = factory->createTextBox();
textBox->display(); // 输出: MacOS 风格文本框 delete button;
delete textBox;
delete factory;
return 0;
}

4.4 三种工厂模式对比

模式 核心目标 扩展性 适用场景
简单工厂 集中创建单一类型的不同对象 差(需修改工厂类) 少量固定类型,无需频繁扩展
工厂方法 将对象创建延迟到子类 好(新增工厂子类) 单一产品,类型可能频繁扩展
抽象工厂 创建多个相关或依赖的对象族 好(新增工厂子类) 多个关联产品,需保持风格一致性

4.5 工厂模式小结

  • 简单工厂:适合简单场景,但违背开闭原则。
  • 工厂方法:解决单一产品的扩展问题。
  • 抽象工厂:解决多产品族的创建问题,强调产品之间的关联性。

根据需求选择合适模式:若产品单一且可能扩展,用工厂方法;若需创建一组关联对象,用抽象工厂;若产品类型固定且简单,用简单工厂。

5. 总结

依赖倒置(DIP)、依赖注入(DI)、控制反转(IoC)和工厂模式是软件设计中紧密相关的概念,它们共同服务于代码的解耦和可维护性。

5.1 关联

  • 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不依赖低层模块,两者都依赖抽象(接口或抽象类)。该思想指导工厂模式、DI 和 IoC 的设计方向。

  • 控制反转(Inversion of Control, IoC):将对象的创建和生命周期管理权从程序内部转移给外部容器(如框架)。例如:依赖由外部容器(如工厂或框架)创建并注入,而不是直接创建依赖。工厂模式和依赖注入 DI 是实现 IoC 的具体方式。

  • 依赖注入(Dependency Injection, DI):通过构造函数、Setter 或接口,将依赖对象被动传递给使用方。是实现 IoC 的具体技术手段。工厂模式常用于生成这些依赖对象。

  • 工厂模式(Factory Pattern):封装具体对象创建逻辑,通过工厂类统一创建对象。是实现 IoC 的手段之一,隐藏实例化细节,支持 DIP 和 DI。是依赖注入 DI 和控制反转 IoC 的底层支撑。

四者共同目标是解耦代码,提升扩展性和可维护性。

5.2 示例全链路

// 1. 遵循 DIP:定义抽象接口
class IStorage { /* ... */ }; // 2. 具体实现
class DatabaseStorage : public IStorage { /* ... */ }; // 3. 工厂模式:封装对象创建
class StorageFactory {
public:
static IStorage* createStorage() { return new DatabaseStorage(); }
}; // 4. 依赖注入:通过构造函数传递对象
class UserService {
private:
IStorage* storage;
public:
UserService(IStorage* storage) : storage(storage) {}
}; // 5. 控制反转:由工厂创建依赖,而非 UserService 内部创建
int main() {
IStorage* storage = StorageFactory::createStorage();
UserService userService(storage); // DI 注入
userService.saveUser();
delete storage;
return 0;
}

依赖倒置 DIP、依赖注入 DI、控制反转 IoC 和工厂模式的更多相关文章

  1. 个人对【依赖倒置(DIP)】、【控制反转(IOC)】、【依赖注入(DI)】浅显理解

    一.依赖倒置(Dependency Inversion Principle) 依赖倒置是面向对象设计领域的一种软件设计原则.(其他的设计原则还有:单一职责原则.开放封闭原则.里式替换原则.接口分离原则 ...

  2. 依赖注入 DI 控制反转 IOC MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  3. ADO.NET .net core2.0添加json文件并转化成类注入控制器使用 简单了解 iTextSharp实现HTML to PDF ASP.NET MVC 中 Autofac依赖注入DI 控制反转IOC 了解一下 C# AutoMapper 了解一下

    ADO.NET   一.ADO.NET概要 ADO.NET是.NET框架中的重要组件,主要用于完成C#应用程序访问数据库 二.ADO.NET的组成 ①System.Data  → DataTable, ...

  4. 浅析“依赖注入(DI)/控制反转(IOC)”的实现思路

    开始学习Spring的时候,对依赖注入(DI)——也叫控制反转(IOC)—— 的理解不是很深刻.随着学习的深入,也逐渐有了自己的认识,在此记录,也希望能帮助其他入门同学更深入地理解Spring.本文不 ...

  5. ASP.NET MVC 中 Autofac依赖注入DI 控制反转IOC 了解一下

    先简单了解一这个几个 名词的意思. 控制反转(IOC) 依赖注入(DI) 并不是某种技术. 而是一种思想.一种面向对象编程法则 什么是控制反转(IOC)?  什么是依赖注入(DI) 可以点击下面链接 ...

  6. 依赖注入 DI 控制反转 IOC 概念 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  7. ASP.NET中IOC容器Autofac(依赖注入DI 控制反转IOC)

    IOC的一个重点是在程序运行中,动态的向某个对象提供它所需要的其他对象.这一点是通过DI来实现的.Autofac则是比较流行的一款IOC容器. IoC和DI有什么关系呢?其实它们是同一个概念的不同角度 ...

  8. 依赖、耦合、解耦、控制反转(IOC)、依赖注入(DI)

    随着net的深入学习,出现了很多概念性的东西需要理解,现在统一记录一下. 1.依赖:现阶段在任何一个有请求作用的系统,都会出现A类调用B类的情况,这时候A类就依赖于B类,A类和B类存在依赖关系. 2. ...

  9. 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。

    轻松学,浅析依赖倒置(DIP).控制反转(IOC)和依赖注入(DI) 2017年07月13日 22:04:39 frank909 阅读数:14269更多 所属专栏: Java 反射基础知识与实战   ...

  10. 依赖倒置原则DIP&控制反转IOC&依赖注入DI

    依赖倒置原则DIP是软件设计里一个重要的设计思想,它规定上层不依赖下层而是共同依赖抽象接口,通常可以是上层提供接口,然后下层实现接口,上下层之间通过接口完全透明交互.这样的好处,上层不会因依赖的下层修 ...

随机推荐

  1. 绞尽脑汁终于搞定/天地图标注点marker旋转/任意角度旋转/无需引入其他框架

    一.前言说明 在其他地图组件中,标注点marker都是可以设置旋转角度的,这个功能其实非常实用,比如飞机移动轨迹,就是需要旋转飞机头飞行,轮船轨迹移动也是,百度地图和腾讯地图是通过调用setRotat ...

  2. An invitation to 3-d vision: from images to geometric models英文pdf下载

    Ma Y, Soatto S, Košecká J, et al. An invitation to 3-d vision: from images to geometric models[M]. N ...

  3. Visual Studio Code启动时总是提示“Code安装似乎损坏。请重新安装。”、标题栏显示“不受支持”等信息的解决办法

    我的VSCode一直提示"Code安装似乎损坏.请重新安装."同时标题栏显示"不受支持"就像这样: 反思了一下,应该是我安装的background插件,把vsc ...

  4. VSTO踩坑记录(1)- 从零开始开发outlook插件

    概述 vsto是微软提供的一种开发office插件的一种技术,现在看来有点落后了,不过项目需要的情况下,总不能跟领导说这活干不了吧?附上官方文档 安装好必备的开发环境,我用的是vs2022,在安装程序 ...

  5. Solution Set -「NOIP Simu.」20221111

    \(\mathscr{A}\sim\) 遗忘十字路   Cover:「CF 1746D」Paths on the Tree.   Tag:「C.性质/结论」   最原始的思路自然是 DP. 令 \(f ...

  6. Solution -「NOI Simu.」逆天题

    \(\mathscr{Description}\)   对于 \(r=0,1,\cdots,n-1\), 设 \(\{1,2,\cdots,nm\}\) 中有 \(f_r\) 个子集满足子集内元素之和 ...

  7. Docker npm install:npm ERR! code UNABLE_TO_GET_ISSUER_CERT_LOCALLY 。。reason: unable to get local issuer certificate 解决办法

    这个是需要证书导致无法连接,临时解决办法是设置 npm set strict-ssl=false 在 Dockerfile文件里的  RUN npm install 之前添加 RUN npm set ...

  8. docker 使用centos镜像运行javaweb

    Docker 是 2014 年最为火爆的技术之一,几乎所有的程序员都听说过它.Docker 是一种"轻量级"容器技术,它几乎动摇了传统虚拟化技术的地位,现在国内外已经有越来越多的公 ...

  9. biancheng-Spring MVC-HandlerAdapter

    二.HandlerAdapter 根据 Handler 来找到支持它的 HandlerAdapter,通过 HandlerAdapter 执行这个 Handler 得到 ModelAndView 对象 ...

  10. w3cschool-R语言 教程

    https://www.w3cschool.cn/r/ R语言教程 R语言是用于统计分析,图形表示和报告的编程语言和软件环境. R语言由Ross Ihaka和Robert Gentleman在新西兰奥 ...