1. 依賴倒置
依賴倒置原則(Dependency Inversion Principle, DIP)是 SOLID 原則中的一項,其核心思想是通過抽象解耦高層模塊和低層模塊,使二者都依賴于抽象而非具體實現。
依賴反轉/倒置的體現:傳統依賴方向是高層模塊直接調用低層模塊,在源碼級別上高層模塊依賴低層細節模塊。而 DIP 通過抽象反轉這種依賴關系,使低層模塊的實現在源碼級別上依賴高層定義的抽象(視為高層模塊的一部分)。
1.1 依賴倒置原則的核心
-
高層模塊不直接依賴低層模塊,二者都應依賴抽象(接口或抽象類,接口由高層模塊定義,視為高層模塊的一部分)。
-
抽象不依賴細節,細節(具體實現)應依賴抽象。
1.2 依賴倒置指導方針
-
變量不可以持有具體類的引用——改用工廠,避免直接使用 new 持有具體類的引用(new 具體類的操作都封裝到工廠中)
-
不要讓類派生自具體類——派生自抽象類或接口,這樣就不依賴具體類了
-
不要覆蓋基類中已經實現的方法——如果這樣,說明不是一個真正適合被繼承的抽象
1.3 示例
場景
-
高層模塊?
ReportGenerator
?需要生成報告,依賴數據獲取功能。 -
低層模塊?
MySQLDatabase
?和?SQLiteDatabase
?提供具體的數據操作。
傳統實現(未遵循 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); // 使用 MySQLreport1.generateReport();ReportGenerator report2(sqliteDb); // 使用 SQLitereport2.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_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
-
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(); // 輸出: 連接到 PostgreSQLdelete 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;
}
文章轉載自:Zijian/TENG
原文鏈接:依賴倒置 DIP、依賴注入 DI、控制反轉 IoC 和工廠模式 - Zijian/TENG - 博客園
體驗地址:引邁 - JNPF快速開發平臺_低代碼開發平臺_零代碼開發平臺_流程設計器_表單引擎_工作流引擎_軟件架構