1. 依赖倒置
依赖倒置原则(Dependency Inversion Principle, DIP)是 SOLID 原则中的一项,其核心思想是通过抽象解耦高层模块和低层模块,使二者都依赖于抽象而非具体实现。
依赖反转/倒置的体现:传统依赖方向是高层模块直接调用低层模块,在源码级别上高层模块依赖低层细节模块。而 DIP 通过抽象反转这种依赖关系,使低层模块的实现在源码级别上依赖高层定义的抽象(视为高层模块的一部分)。
1.1 依赖倒置原则的核心
高层模块不直接依赖低层模块,二者都应依赖抽象(接口或抽象类,接口由高层模块定义,视为高层模块的一部分)。
抽象不依赖细节,细节(具体实现)应依赖抽象。
1.2 依赖倒置指导方针
变量不可以持有具体类的引用——改用工厂,避免直接使用 new 持有具体类的引用(new 具体类的操作都封装到工厂中)
不要让类派生自具体类——派生自抽象类或接口,这样就不依赖具体类了
不要覆盖基类中已经实现的方法——如果这样,说明不是一个真正适合被继承的抽象
1.3 示例
场景
传统实现(未遵循 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;};
复制代码
2、低层模块实现接口:
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 数据"; }};
复制代码
3、高层模块依赖抽象:
class ReportGenerator {private: Database& db; // 依赖抽象接口public: ReportGenerator(Database& database) : db(database) {} // 依赖注入 void generateReport() { db.connect(); auto data = db.fetchData(); std::cout << "报告数据: " << data << std::endl; }};
复制代码
class ReportGenerator {private: Database& db; // 依赖抽象接口public: ReportGenerator(Database& database) : db(database) {} // 依赖注入 void generateReport() { db.connect(); auto data = db.fetchData(); std::cout << "报告数据: " << data << std::endl; }};
复制代码
4、使用示例:
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` 的代码。
- **依赖注入**:通过接口解耦,仅需注入不同实现。
```cppclass UserService {private: Database& db; // 依赖抽象public: UserService(Database& db) : db(db) {}};
复制代码
2. 可测试性
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_ptr 或 std::unique_ptr。
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
关系:
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)
核心思想
适用场景
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;}
复制代码
文章转载自:Zijian/TENG
原文链接:https://www.cnblogs.com/tengzijian/p/18775674
体验地址:http://www.jnpfsoft.com/?from=001YH
评论