依赖倒置 DIP、依赖注入 DI、控制反转 IoC 和工厂模式
月色朦胧 /
03-16 /
5点赞 /
0评论 /
768阅读
## 1. 依赖倒置
依赖倒置原则(Dependency Inversion Principle, DIP)是 SOLID 原则中的一项,其**核心思想**是通过**抽象**解耦高层模块和低层模块,使**二者都依赖于抽象而非具体实现**。
**依赖反转/倒置的体现**:传统依赖方向是高层模块直接调用低层模块,在源码级别上高层模块依赖低层细节模块。而 DIP 通过抽象反转这种依赖关系,使低层模块的实现在源码级别上依赖高层定义的抽象(**视为高层模块的一部分**)。
### 1.1 依赖倒置原则的核心
1. **高层模块不直接依赖低层模块**,二者都应依赖抽象(接口或抽象类,接口由高层模块定义,视为高层模块的一部分)。
2. **抽象不依赖细节**,细节(具体实现)应依赖抽象。
### 1.2 依赖倒置指导方针
- 变量不可以持有具体类的引用——改用工厂,**避免直接使用 new 持有具体类的引用(new 具体类的操作都封装到工厂中)**
- 不要让类派生自具体类——派生自抽象类或接口,这样就不依赖具体类了
- 不要覆盖基类中已经实现的方法——如果这样,说明不是一个真正适合被继承的抽象
### **1.3 示例**
#### **场景**
- 高层模块 `ReportGenerator` 需要生成报告,依赖数据获取功能。
- 低层模块 `MySQLDatabase` 和 `SQLiteDatabase` 提供具体的数据操作。
#### **传统实现(未遵循 DIP)**
```csharp
// 低层模块:直接依赖具体实现
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. **定义抽象接口**:
```csharp
class Database {
public:
virtual ~Database() = default;
virtual void connect() = 0;
virtual std::string fetchData() = 0;
};
```
1. **低层模块实现接口**:
```csharp
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. **高层模块依赖抽象**:
```csharp
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. **使用示例**:
```csharp
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. **构造函数注入(最常用)**
通过构造函数传递依赖,确保对象在创建时即具备完整依赖。
```csharp
class NotificationService {
private:
MessageSender& sender; // 依赖抽象接口
public:
NotificationService(MessageSender& sender) : sender(sender) {} // 构造函数注入
void sendMessage(const std::string& msg) {
sender.send(msg);
}
};
```
#### 2. **属性注入(Setter 注入)**
通过公开的成员属性或 Setter 方法动态设置依赖。
```csharp
class NotificationService {
public:
void setSender(MessageSender& sender) { // Setter 注入
this->sender = &sender;
}
private:
MessageSender* sender;
};
```
#### 3. **方法注入**
通过方法参数传递依赖,适用于临时或局部依赖。
```csharp
class NotificationService {
public:
void sendMessage(MessageSender& sender, const std::string& msg) { // 方法注入
sender.send(msg);
}
};
```
### 2.3 为什么需要依赖注入?
#### 1. **解耦与可维护性**
- **传统代码**:对象内部直接创建依赖,导致紧耦合。
```csharp
class UserService {
private:
MySQLDatabase db; // 直接依赖具体类
};
```
```csharp
若需改用 `SQLiteDatabase`,必须修改 `UserService` 的代码。
- **依赖注入**:通过接口解耦,仅需注入不同实现。
class UserService {
private:
Database& db; // 依赖抽象
public:
UserService(Database& db) : db(db) {}
};
```
#### 2. **可测试性**
- 依赖注入允许在测试时替换为 Mock 对象。
```csharp
class MockDatabase : public Database { /* 模拟实现 */ };
TEST(UserServiceTest) {
MockDatabase mockDb;
UserService service(mockDb); // 注入 Mock 对象
// 执行测试...
}
```
#### 3. **扩展性**
- 新增功能时,只需实现新依赖并注入,无需修改现有代码。
```csharp
class MongoDB : public Database { /* 新数据库实现 */ };
MongoDB mongoDb;
UserService service(mongoDb); // 直接注入新依赖
```
### 2.4 C++ 依赖注入的实践技巧
#### 1. **使用智能指针管理生命周期**
避免裸指针导致的内存泄漏,使用 `std::shared_ptr` 或 `std::unique_ptr`。
cpp
```csharp
class NotificationService {
private:
std::shared_ptr sender; // 智能指针管理依赖
public:
NotificationService(std::shared_ptr sender) : sender(sender) {}
};
```
#### 2. **结合工厂模式**
通过工厂类集中管理依赖的创建逻辑。
```csharp
class SenderFactory {
public:
static std::shared_ptr createSender(const std::string& type) {
if (type == "email") return std::make_shared();
else return std::make_shared();
}
};
// 使用工厂创建依赖
auto sender = SenderFactory::createSender("email");
NotificationService service(sender);
```
#### 3. **依赖注入容器(IoC Container)**
在复杂项目中,使用容器自动管理依赖关系(如 Boost.DI)。
```csharp
#include
namespace di = boost::di;
// 定义接口和实现
class Database { /* ... */ };
class MySQLDatabase : public Database { /* ... */ };
// 配置容器
auto injector = di::make_injector(
di::bind().to()
);
// 自动注入依赖
class UserService {
public:
UserService(Database& db) { /* ... */ }
};
UserService service = injector.create();
```
### 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++ 示例**
```csharp
// 抽象产品
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++ 示例**
```csharp
// 抽象产品
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++ 示例**
```csharp
// 抽象产品:按钮
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 示例全链路
```csharp
// 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;
}
```
本文来自投稿,不代表本站立场,如若转载,请注明出处:http//www.knowhub.vip/share/2/851
- 热门的技术博文分享
- 1 . ESP实现Web服务器
- 2 . 从零到一:打造高效的金仓社区 API 集成到 MCP 服务方案
- 3 . 使用C#构建一个同时问多个LLM并总结的小工具
- 4 . .NET 原生驾驭 AI 新基建实战系列Milvus ── 大规模 AI 应用的向量数据库首选
- 5 . 在Avalonia/C#中使用依赖注入过程记录
- 6 . [设计模式/Java] 设计模式之工厂方法模式
- 7 . 5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
- 8 . SQL 中的各种连接 JOIN 的区别总结!
- 9 . JavaScript 中防抖和节流的多种实现方式及应用场景
- 10 . SaltStack 远程命令执行中文乱码问题
- 11 . 推荐10个 DeepSeek 神级提示词,建议搜藏起来使用
- 12 . C#基础:枚举、数组、类型、函数等解析
- 13 . VMware平台的Ubuntu部署完全分布式Hadoop环境
- 14 . C# 多项目打包时如何将项目引用转为包依赖
- 15 . Chrome 135 版本开发者工具(DevTools)更新内容
- 16 . 从零创建npm依赖,只需执行一条命令
- 17 . 关于 Newtonsoft.Json 和 System.Text.Json 混用导致的的序列化不识别的问题
- 18 . 大模型微调实战之训练数据集准备的艺术与科学
- 19 . Windows快速安装MongoDB之Mongo实战
- 20 . 探索 C# 14 新功能:实用特性为编程带来便利