依賴注入(Dependency Injection, DI)的核心概念和解決的核心問題

核心概念:

依賴注入是一種設計模式,也是實現控制反轉(Inversion of Control, IoC) 原則的一種具體技術。其核心思想是:

  1. 解耦: 將一個類(客戶端)所依賴的其他類或服務(依賴項)的創建和管理職責,從該類內部移除
  2. 反轉控制: 將依賴項的創建和提供(注入)的控制權,反轉給外部(通常是框架、容器或調用者)。
  3. 注入方式: 依賴項通過構造函數屬性/Setter方法接口方法等方式傳遞(注入)給需要它的類。

簡單來說: 不是讓對象自己去找它所依賴的東西(比如自己 new 一個),而是由外部(“注入器”)把依賴的東西“喂”給它。

解決的核心問題:

依賴注入主要解決軟件開發中常見的幾個痛點:

  1. 緊耦合(Tight Coupling): 當類 A 在其內部直接實例化它所依賴的類 B(例如 B b = new B();)時,A 和 B 就緊密耦合在一起。這意味著:

    • 修改困難: 如果想替換 B 的實現(比如換成更高效的 BImpl2),必須修改 A 的源代碼。
    • 難以測試: 測試 A 時,無法輕松地將 B 替換為一個模擬對象(Mock)或樁對象(Stub)來進行隔離測試(因為 A 內部硬編碼了 new B())。測試 A 會不可避免地觸發真實的 B,這可能導致測試速度慢、依賴外部環境(數據庫、網絡)、或產生副作用。
    • 缺乏靈活性: 難以在運行時根據配置或條件動態切換依賴項的實現。
    • 違反單一職責原則: 類 A 不僅要完成自己的核心邏輯,還要負責創建和管理 B 的生命周期。
  2. 可測試性差: 如上所述,緊耦合使得單元測試(孤立地測試一個單元)變得非常困難。

  3. 代碼重復和難以維護: 如果多個類都需要同一個依賴項(比如一個數據庫連接池或日志服務),并且各自負責創建它,會導致創建邏輯重復,難以統一管理和配置。

  4. 生命周期管理復雜: 當依賴關系變得復雜(如依賴依賴的依賴)時,手動管理對象的創建順序、作用域(單例、請求作用域等)和銷毀變得異常繁瑣且容易出錯。

依賴注入如何解決這些問題?

  • 解耦: 類 A 不再關心如何創建 B。它只聲明“我需要一個實現了某接口(或符合某基類)的東西”。
  • 可測試性: 在測試 A 時,你可以輕松地“注入”一個模擬的 B(MockB),這個 MockB 完全在你的控制之下,用于驗證 A 是否正確地調用了 B 的方法,而無需啟動真實的 B(如數據庫、網絡服務)。
  • 靈活性: 依賴項的具體實現可以在外部配置(如配置文件、代碼配置)。更換實現只需要修改注入器的配置,無需修改使用它的類(A)。
  • 可維護性: 創建邏輯集中在注入器(如 DI 容器)中,避免重復。依賴關系清晰聲明(通常在構造函數或屬性上),代碼更易理解。
  • 生命周期管理: DI 容器通常提供強大的生命周期管理功能(單例、瞬態、作用域),自動處理依賴項的創建和銷毀。

舉例說明(傳統方式 vs. 依賴注入方式):

場景: 一個用戶登錄服務 (LoginService) 需要在登錄成功后發送通知。通知方式可能是郵件 (EmailNotifier) 或短信 (SmsNotifier)。

1. 傳統方式(緊耦合 - 自己創建依賴):

// 郵件通知實現
public class EmailNotifier {public void sendNotification(String message) {// 實際發送郵件的復雜邏輯System.out.println("Sending email: " + message);}
}// 登錄服務 - 內部直接創建 EmailNotifier
public class LoginService {private EmailNotifier notifier; // 直接依賴具體實現類public LoginService() {this.notifier = new EmailNotifier(); // 緊耦合:在構造函數內部創建依賴}public void login(String username, String password) {// ... 驗證邏輯 ...// 登錄成功后發送通知notifier.sendNotification("User " + username + " logged in successfully.");}
}// 使用登錄服務
public class Main {public static void main(String[] args) {LoginService loginService = new LoginService(); // LoginService內部已經綁定了EmailNotifierloginService.login("alice", "password123");}
}

傳統方式的問題:

  • 緊耦合: LoginService 直接依賴具體的 EmailNotifier,并在其構造函數中硬編碼了 new EmailNotifier()
  • 難以切換通知方式: 如果想改用 SmsNotifier,必須修改 LoginService 的源代碼(把 new EmailNotifier() 改成 new SmsNotifier()),違反了開閉原則(對擴展開放,對修改關閉)。
  • 難以測試: 測試 login 方法時,它會真的嘗試發送一封郵件!這很慢,可能失敗(如果沒有郵件服務器配置),并且測試關注點應該是登錄邏輯是否正確,而不是郵件發送。你無法輕松地用模擬對象替換 EmailNotifier

2. 依賴注入方式(解耦 - 依賴由外部提供):

// 1. 定義通知接口 (抽象)
public interface Notifier {void sendNotification(String message);
}// 2. 郵件通知實現 (具體實現1)
public class EmailNotifier implements Notifier {@Overridepublic void sendNotification(String message) {System.out.println("Sending email: " + message);}
}// 3. 短信通知實現 (具體實現2) - 新增很容易
public class SmsNotifier implements Notifier {@Overridepublic void sendNotification(String message) {System.out.println("Sending SMS: " + message);}
}// 4. 登錄服務 - 依賴抽象(接口),通過構造函數注入
public class LoginService {private Notifier notifier; // 依賴抽象接口,而不是具體類// 構造函數注入:依賴項通過參數傳入public LoginService(Notifier notifier) {this.notifier = notifier; // 接收外部傳入的Notifier實現}public void login(String username, String password) {// ... 驗證邏輯 ...// 登錄成功后發送通知 (通過接口調用)notifier.sendNotification("User " + username + " logged in successfully.");}
}// 5. 使用登錄服務 (手動注入 - 模擬"注入器"的角色)
public class Main {public static void main(String[] args) {// 決定使用哪種通知方式 (配置點)Notifier emailNotifier = new EmailNotifier();// Notifier smsNotifier = new SmsNotifier(); // 切換通知方式只需改這一行!// 創建LoginService,并將依賴項(Notifier)注入給它LoginService loginService = new LoginService(emailNotifier); // 注入Email實現// LoginService loginService = new LoginService(smsNotifier); // 注入SMS實現loginService.login("bob", "securePass");}
}// 6. 測試登錄服務 (使用Mock框架如Mockito)
public class LoginServiceTest {@Testpublic void testLoginSuccessSendsNotification() {// 1. 創建Notifier的模擬對象(Mock)Notifier mockNotifier = Mockito.mock(Notifier.class);// 2. 創建LoginService,注入模擬的NotifierLoginService loginService = new LoginService(mockNotifier);// 3. 執行登錄操作loginService.login("testUser", "testPass");// 4. 驗證:mockNotifier的sendNotification方法是否被正確調用了一次Mockito.verify(mockNotifier, Mockito.times(1)).sendNotification(Mockito.contains("testUser")); // 驗證消息包含用戶名}
}

依賴注入方式的優點:

  • 解耦: LoginService 只依賴于 Notifier 接口,完全不知道也不關心具體是 EmailNotifier 還是 SmsNotifier。它只關心接口契約。
  • 易于切換實現: 在程序入口(Main 或配置中),只需改變注入給 LoginService 的具體 Notifier 實例(如 new EmailNotifier()new SmsNotifier()),無需修改 LoginService 本身的代碼。符合開閉原則。
  • 易于測試:
    • 在單元測試 LoginServiceTest 中,我們可以輕松地創建一個 Notifier 的模擬對象 (mockNotifier)。
    • 將這個模擬對象注入到 LoginService 中。
    • 執行 login 方法。
    • 驗證 login 方法是否正確地調用了 mockNotifier.sendNotification(...) 方法,并檢查了傳遞的參數。整個過程完全隔離,沒有真實的郵件或短信發送! 測試快速、可靠、無副作用。
  • 可擴展性強: 添加新的通知方式(如 PushNotifier),只需實現 Notifier 接口并在注入點使用它即可。LoginService 完全不需要改動。
  • 職責清晰: LoginService 只負責登錄邏輯,Notifier 負責發送通知,創建 Notifier 實例的職責由外部(如 Main 或 DI 容器)承擔。符合單一職責原則。

依賴注入的常見方式:

  1. 構造函數注入(最推薦): 依賴項通過類的構造函數傳入。優點:強制要求依賴,保證對象在構造完成后就是完整的、可用的狀態;依賴關系明確;方便不可變(immutable)對象的創建。
  2. Setter方法注入(屬性注入): 依賴項通過類的公共Setter方法設置。優點:比較靈活,可以在對象創建后改變依賴(但通常不推薦頻繁改變)。缺點:對象可能在一段時間內處于依賴不完整的狀態。
  3. 接口注入: 定義一個包含注入方法的接口,需要依賴的類實現這個接口,注入器通過該接口方法注入依賴。這種方式相對少見。

依賴注入容器(DI Container/IoC Container):

在實際的大型項目中,手動管理所有的依賴注入(像上面 Main 里那樣)會變得非常繁瑣。這時通常會使用依賴注入容器(如 Spring Framework for Java, .NET Core DI, Guice, Dagger 等)。容器的職責是:

  • 注冊(Register): 告訴容器有哪些類型(接口和它們的實現類)需要管理,以及它們的生命周期(單例、每次請求新實例等)。
  • 解析(Resolve): 當需要一個對象(如 LoginService)時,容器會自動查找它的依賴(Notifier),創建依賴(或使用已存在的實例,如單例),并將依賴注入到目標對象中,最后返回組裝好的、完全可用的目標對象實例。

使用容器后,創建對象的復雜性(對象圖的構建)就完全交給了容器管理。

總結:

特性傳統方式 (緊耦合)依賴注入方式 (松耦合)
依賴創建類內部創建 (new)外部創建并注入
耦合度 (依賴具體類) (依賴抽象接口/基類)
可測試性 (難以隔離測試) (易于注入Mock進行單元測試)
靈活性 (修改依賴需改代碼) (通過配置/注入點輕松切換實現)
可維護性 (職責混雜,依賴關系隱式) (職責清晰,依賴關系顯式聲明)
擴展性 (添加新實現需修改客戶端) (添加新實現只需注冊并注入)
核心原則違反IoC、開閉原則、單一職責遵循IoC、開閉原則、單一職責、依賴倒置

依賴注入通過將對象的依賴關系與其創建邏輯分離,極大地提高了代碼的松耦合性、可測試性、可維護性和靈活性,是現代軟件開發中一項至關重要的設計模式和技術。 它通常與面向接口編程和單元測試實踐緊密結合。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/88988.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/88988.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/88988.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Reactor Schedulers

Reactor 是一個基于響應式編程的庫,它提供了豐富的調度器(Schedulers)機制,用于管理異步操作的執行環境。Schedulers 是 Reactor 中的核心組件之一,它們允許開發者靈活地控制操作符和訂閱操作在哪個線程上執行&#xf…

設備樹引入

一、設備樹的基本知識 1、什么是設備樹?為什么會有設備樹? 2011年,Linux之父Linus Torvalds發現這個問題后,就通過郵件向ARM-Linux開發社區發了一封郵件,不禁的發出了一句“This whole ARM thing is a f*cking pain i…

【數據標注師】3D標注

目錄 一、 **3D標注知識體系框架**二、 **五階能力培養體系**? **階段1:空間認知筑基(2-3周)**? **階段2:核心標注技能深化**? **階段3:復雜場景解決方案**? **階段4:領域深度專精? **階段5&#xff1…

華為HN8145V光貓改華為藍色公版界面,三網通用,xgpon公版光貓

咸魚只賣20多元一個,還是xgpon的萬兆貓,性價比不錯哦 除了沒有2.5G網口,其他還行。 改成公版光貓后,運營商是無法納管光貓,無法后臺修改光貓數據及超密。 華為 HN8145V 光貓具有以下特點: 性能方面 高速接…

【LeetCode 熱題 100】438. 找到字符串中所有字母異位詞——(解法二)定長滑動窗口+數組

Problem: 438. 找到字符串中所有字母異位詞 題目:給定兩個字符串 s 和 p,找到 s 中所有 p 的 異位詞 的子串,返回這些子串的起始索引。不考慮答案輸出的順序。 【LeetCode 熱題 100】438. 找到字符串中所有字母異位詞——(解法一&…

PAC 學習框架:機器學習的可靠性工程

PAC(Probably Approximately Correct) 是機器學習理論的核心框架,用于量化學習算法的可靠性。它回答了一個關鍵問題: “需要多少訓練樣本,才能以較高概率學到一個近似正確的模型?” 一、PAC 名稱拆解 術語…

嵌入式C語言數組:數組/字符數組

1. 數組 1.1 一維數組 數組是一串連續的地址; 數組名是地址常量,代表數組的起始地址; sizeof(數組名) 可得出數組的總內存空間; C 語言對數組不做越界檢查,使用時應注意; 數組不…

變長字節的數字表示法vb224

開始 數字有大有小,用多少字節表示呢? 本文描述的方案,采用變化的長度。vb是varying bytes的意思,224是表示它特征的一個數。 第一版: 每個字節8比特,最高的1比特用來表示“是否連續”,0表示…

ByteMD+CozeAPI+Coze平臺Agent+Next搭建AI輔助博客撰寫平臺(邏輯清楚,推薦!)

背景: 現在主流的博客平臺AI接入不夠完善,如CSDN接入的AI助手不支持多模態數據的交互、稀土掘金的編輯器AI功能似乎還沒能很好接入(哈哈哈,似乎在考慮布局什么?) 痛點分析: 用戶常常以截圖的形式…

【數據標注師】關鍵詞標注

目錄 一、 **理解關鍵詞標注的核心邏輯**1. **三大標注原則**2. **關鍵詞類型體系** 二、 **四階訓練體系**? **階段1:基礎規則內化**? **階段2:語義濃縮訓練**? **階段3:場景化標注策略**? **階段4:工具效率提升** 三、 **五…

for each循環語句

for each循環語句 for each.....nextFor Each 的案例 for each…next 1、循環對象合集 worksheets workbooks range range("區域")selection (選中的區域)usedrange或者currentregion 返回的單元格區域格式: for each 變量名 in 對象集合(范圍)循環內容…

基于LQR控制器的六自由度四旋翼無人機模型simulink建模與仿真

目錄 1.課題概述 2.系統仿真結果 3.核心程序 4.系統原理簡介 5.參考文獻 6.完整工程文件 1.課題概述 四旋翼無人機因其結構簡單、機動性強和成本低廉等特點,在航拍測繪、物流運輸、災害救援等領域得到廣泛應用。六自由度(3維平移3維旋轉&#xff0…

vftp centos 離線部署

install_ftp_offline.sh vsftpd-3.0.2-28.el7.x86_64.rpm #!/bin/bash# 一鍵安裝配置vsftpd腳本(開放根目錄,禁用chroot)# 安裝vsftpd RPM包 echo "正在安裝vsftpd..." rpm -ivh vsftpd-3.0.2-28.el7.x86_64.rpm if [ $? -ne 0 …

【數據標注】事件標注1

目錄 **一、 深入理解事件標注的核心概念****二、 系統學習:從理論到實踐****1. 吃透標注指南****2. 語言學基礎補充****3. 事件結構解析訓練** **三、 分階段實踐:從簡單到復雜****階段1:基礎標注訓練****階段2:進階挑戰****階段…

在 Ansys Electronics Desktop 中啟用額外的 CPU 內核和 GPU

Ansys Electronics Desktop (AEDT) 可以通過利用多個 CPU 內核和 GPU 加速來顯著縮短仿真時間。但是,啟用其他計算資源除了基本求解器許可證外,還需要適當的高性能計算 (HPC) 許可證。 默認情況下,基本許可證最多允許使用 4 個內核,而無需任何其他 HPC 許可。借助 Ans…

R語言機器學習算法實戰系列(二十六)基于tidymodels的XGBoost二分類器全流程實戰

禁止商業或二改轉載,僅供自學使用,侵權必究,如需截取部分內容請后臺聯系作者! 文章目錄 介紹加載R包數據準備數據探索轉換因子查看屬性相關性配對圖PCA 可視化缺失值、異常值處理 & 特征標準數據分割構建模型與調參模型評估模型可解釋性(變量重要性、SHAP、DALEX)變量…

零基礎langchain實戰一:模型、提示詞和解析器

一,使用python調取大模型api 1,獲取api_key 獲取api_key 在各個大模型的官網中獲取。 2,設置api_key 方式一: 在系統環境中可直接執行python代碼:這里以deepseek為例 import os os.environ["DEEPSEEK_API_…

Pytorch分布式通訊為什么要求Tensor連續(Contiguous)

參考資料: https://github.com/pytorch/pytorch/issues/73515 https://www.cnblogs.com/X1OO/articles/18171700 由于業務原因,需要在Pytorch代碼中使用分布式通訊來把計算負載平均到多張顯卡上。在無數次確認我的業務代碼沒問題之后,我開始把…

關于前端頁面上傳圖片檢測

依賴于前文,linux系統上部署yolo識別圖片,遠程宿主機訪問docker全流程(https://blog.csdn.net/yanzhuang521967/article/details/148777650?spm1001.2014.3001.5501) fastapi把端口暴露出來 后端代碼 from fastapi import FastAPI, UploadFile, File, HTTPExcep…

第十三章---軟件工程過程管理

僅供參考 文章目錄 一、Gantt圖是做什么的。二、軟件配置的概念 一、Gantt圖是做什么的。 Gantt 圖(甘特圖)是軟件項目管理中用于進度安排和可視化管理的重要工具,主要用于展示任務的時間安排、進度狀態及任務之間的依賴關系 Gantt 圖是一種…