設計模式學習筆記 - 設計原則 - 5.依賴反轉原則(控制反轉、依賴反轉、依賴注入)

前言

今天學習 SOLID 中的最后一個原則,依賴反轉原則。

本章內容,可以帶著如下幾個問題:

  • “依賴反轉” 這個概念指的是 “誰跟誰” 的 “什么依賴” 被反轉了? “反轉” 這兩個字該如何理解。
  • 我們還經常聽到另外兩個概念:“控制反轉” 和 “依賴注入”。這兩個概念跟 “依賴反轉” 有什么區別和聯系嗎?它們說的是同一個事情嗎?
  • 如果你熟悉 Java 語言,那 Spring 框架中的 IOC 跟這些概念有什么關系?

控制反轉(IOC)

在講依賴反轉原則之前,我們先講下“控制反轉”。

如果你是Java工程師的話,暫時別把這個 “IOC” 和 Spring 框架的 “IOC” 聯系在一起。關于 Spring 的 IOC ,我們待會還會講到。

先通過一個例子來查看,什么是控制反轉。

public class UserServiceTest {public static final boolean doTest() {// ...}public static void main(String[] args) { // 這部分邏輯可以放到框架中if (doTest()) {System.out.println("Test succeeded.");} else {System.out.println("Test failed.");}}
}

在上面的代碼中,所有的流程都由程序員來控制。如果我們抽象出下面這樣一個框架,再來看,如何利用框架來實現同樣的功能。

public abstract class TestCase {public void run() {if (doTest()) {System.out.println("Test succeeded.");} else {System.out.println("Test failed.");}}public abstract void doTest();
}public class JunitApplication() {private static final List<TestCase> testCases = new ArrayList<>();public static void register(TestCase testCase) {testCases.add(testCase);}public static final void main(String[] args) {for (TestCase testCase : testCases) {testCase.run();}}
}

這個簡化版本的測試框架引入到工程之后,我們只需要在框架預留的擴展點,即 TestCase 類中的 doTest() 抽象函數中,填充具體的測試代碼就可以實現之前的功能了,完全不需要寫負責執行流程的 main() 函數了。具體代碼如下所示:

public class UserServiceTest extends TestCase {@Overridepublic void doTest() {// ...}
}// 注冊操作還可以通過配置的方式來實現,不需要程序員顯示調用 register()
JunitApplication.register(new UserServiceTest());

上面例子,就是典型的通過框架來實現“控制反轉”的例子。框架提供了一個可擴展的代碼骨架,用來封裝對象、管理整個執行流程。程序員利用框架進行開發的時候,只需要往預留的擴展點上,添加跟自己業務相關的代碼,就可以利用框架驅動整個程序流程的執行。

  • 這里的“控制” 指的是對程序執行流程的控制
  • 而“反轉”是指流程的控制權從程序員“反轉”到了框架。在沒有使用框架前,程序員自己控制整個程序的執行;使用框架后,整個執行流程可以通過框架來控制。

實際上,實現控制反轉的方法有很多,除了上面的例子中類似模板設計模式的方法之外,還有依賴注入等方法。所以,控制反轉并不是一種具體的實現技巧,而是一種比較籠統的設計思想,一般用來指導框架層面的設計

依賴注入(DI)

依賴注入和控制反轉相反,它是一種編碼技巧。

依賴注入英文翻譯為:Dependency Injection,縮寫為 DI。

什么是依賴注入? 用一句話來概括就是:不通過 new() 的方式在類內部創建依賴類的對象,而是將依賴類對象在外部建好之后,通過構造函數、函數參數等方式傳遞(或注入)給類適用

通過一個例子來解釋下。在這個例子中, Notification 類負責消息推送,依賴 MessageSender 類來實現推送商品促銷、驗證碼等消息給用戶。我們分別用依賴注入和非依賴注入兩種方式來實現一下。具體的實現代碼如下所示:

// 非依賴注入實現方式
public class Notification {private MessageSender messageSender;public Notification() {this.messageSender = new MessageSender(); // 此處有點像hardcode}public void sendMessage(String cellPhone, String message) {// 省略校驗邏輯等...this.messageSender.sendMessage(cellPhone, message);}
}public class MessageSender {public void sendMessage(String cellPhone, String message) {// ...}
}// 使用Notification
Notification notification = new Notification();// 依賴注入實現方式
public class Notification {private MessageSender messageSender;// 通過構造函數將messageSender傳遞進來public Notification(MessageSender messageSender) {this.messageSender = messageSender;}public void sendMessage(String cellPhone, String message) {// 省略校驗邏輯等...this.messageSender.sendMessage(cellPhone, message);}
}public class MessageSender {public void sendMessage(String cellPhone, String message) {// ...}
}// 使用Notification
MessageSender messageSender = new MessageSender();
Notification notification = new Notification(messageSender);

通過依賴注入的方式將對象傳遞進來,這樣就提高了代碼的擴展性,我們可以靈活地替換依賴的類。這一點在我們之前將“開閉原則”的也提到過。當然,上面代碼還有繼續優化的空間,把 MessageSender 定義成接口,基于接口而非實現編程。改造后代碼如下:

public class Notification {private MessageSender messageSender;public Notification(MessageSender messageSender) {this.messageSender = messageSender;}public void sendMessage(String cellPhone, String message) {this.messageSender.sendMessage(cellPhone, message);}
}public interface MessageSender {void sendMessage(String cellPhone, String message);
}public class SmsSender implements MessageSender {@Overridepublic void sendMessage(String cellPhone, String message) { /**/ }
}public class InboxSender implements MessageSender {@Overridepublic void sendMessage(String cellPhone, String message) { /**/ }
}// 使用Notification
MessageSender messageSender = new SmsSender();
Notification notification = new Notification(messageSender);

實際上,只需掌握剛剛舉的例子,就掌握了依賴注入。盡管依賴注入非常簡單,但卻非常有用。

依賴注入框架(DI Framework)

弄懂了什么是“依賴注入”,在來看下,什么“依賴注入框架”。還是借用剛剛例子來解釋。

在采用依賴注入實現的 Notification 類中,雖然不需要使用類似 hard code 的方式,在類內部通過 new 來創建 MessageHandler 對象,但是這個創建對象、組裝(或注入)對象的工作,僅僅是被移動到了上層代碼而已,還是需要我們程序員自己來實現。具體代碼如下:

public class Demo {public static void main(String[] args) {MessageSender messageSender = new SmsSender(); // 創建對象Notification notification = new Notification(messageSender); // 依賴注入notification.sendMessage("13910221123", "短信驗證碼:2345");}
}

在實際開發中,一些項目可能會涉及幾十、上百、甚至幾百個類,類對象的創建的依賴注入會變得非常復雜。如果這部分工作由程序員自己寫代碼來完成,容易出錯且開發成本比較高。而創建和依賴注入的工作,本身和業務無關,完全可以抽象成框架來自動完成。

這個框架就是“依賴注入框架”。只需要通過依賴注入框架提供的擴展點,簡單配置以下所有需要創建的類對象、類與類之間的依賴關系,就可以實現由框架來自動創建對象、管理對象的生命這周期、依賴注入等事情。

實際上,現成的依賴注入框架有很多,比如 Google Guice、Java Spring、Pico Container 等。

不過,Spring 框架自己聲稱是控制反轉容器(Inversion of Control Container)。
實際上,這兩種說法都沒錯。只是控制反轉容器這種表述是一種非常寬泛的描述,除了依賴注入,還有模板模式等,而 Spring 框架的控制反轉主要是通過依賴注入來實現的。

依賴反轉原則(DIP)

接下來講一下本章的主角:依賴反轉原則。依賴反轉原則的英文翻譯是 Dependency Inversion Principle,縮寫為 DIP。有時也翻譯成依賴倒置原則。

英文原文描述:

High-level modules shouldn’t depend on low-level modules。Both modules should depend on abstractions shouldn’t depend on details。Details depend on abstractions.

翻譯成中文,大概意思是: 高層模塊不要依賴低層模塊高層模塊和低層模塊應該通過抽象來相互依賴。此外,抽象不要依賴具體實現細節具體實現細節依賴抽象

所謂高層模塊和低層模塊的劃分,簡單來說,在調用鏈上,調用者屬于高層,被調用者屬于低層。在平時的業務代碼開發中,高層模塊依賴底層模型是沒有任何問題的。實際上,這條原則主要用來指導框架層面的設計,跟前面講到的控制反轉類似。我們拿 Tomcat 這個 Servlet 容器作為例子來解釋下。

Tomcat 是運行 Java Web 應用程序的容器。我們編寫 Web 應用程序代碼只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器調用執行。

  • 按照之前的劃分原則,Tomcat 就是高層模塊,我們編寫的 Web 應用程序屬于低層模塊。
  • Tomcat 和 應用程序之間并沒有直接的依賴關系,兩者依賴同一個抽象,也就是 Servlet 規范。
  • Servlet 規范不依賴具體的 Tomcat 容器和應用程序的實現細節。
  • 而 Tomcat 容器和應用程序依賴 Servlet 規范。

總結

1.控制反轉

控制反轉是一個比較抽象的設計思想,并不是具體的實現方法,一般指導框架層面的設計。

  • “控制” 指的是對程序執行流程的控制
  • “反轉” 指的是在沒有使用框架之前,程序員自己控制整個程序的執行。在使用框架之后,整個程序的執行流程通過框架來控制。流程控制權從程序員 “反轉” 給了框架

2.依賴注入

依賴注入和控制反轉相反,它是一種具體的編碼技巧。我們不通過 new 的方式在類內部創建依賴類的對象,而是將依賴類的對象在外部創建好之后,通過構造函數、函數參數等方式注入給類適用。

3.依賴注入框架

我們通過依賴注入框架提供的擴展點,簡單配置下需要的類及類與類之間的依賴關系,就可以實現由框架來自動創建對象、管理對象生命周期、依賴注入等原本需要程序員來做的事情。

4.依賴反轉原則

依賴反轉原則,也叫依賴倒置原則。這條原則跟控制反轉有點類似,主要用來指導框架層面的設計。

  • 高層模型不依賴低層模塊
  • 它們共同依賴同一個抽象
  • 抽象不要依賴具體實現細節
  • 具體實現細節依賴抽象。

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

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

相關文章

【分塊三維重建】【slam】LocalRF:逐步優化的局部輻射場魯棒視圖合成(CVPR 2023)

項目地址&#xff1a;https://localrf.github.io/ 題目&#xff1a;Progressively Optimized Local Radiance Fields for Robust View Synthesis 來源&#xff1a;KAIST、National Taiwan University、Meta 、University of Maryland, College Park 提示&#xff1a;文章用了s…

【Spring】20 解析Spring注解驅動的容器配置

文章目錄 注解 vs. XMLJavaConfig選項注解配置注解注入順序注解處理器實際運用總結 Spring 框架一直以 XML 配置為主導&#xff0c;然而隨著注解驅動配置的引入&#xff0c;我們不禁思考&#xff1a;是注解配置優于 XML 呢&#xff0c;還是反之&#xff1f;本篇博客將介紹 Spri…

如何將一個遠程git的所有分支推到另一個遠程分支上

如何將一個遠程git的所有分支推到另一個遠程分支上 最初有 12 個分支 執行 git remote add 遠程名 遠程git地址 git push 遠程名 --tags "refs/remotes/origin/*:refs/heads/*"之后就變成 26個分支

小項目:2024/3/2

一、TCP機械臂測試 代碼&#xff1a; #include <myhead.h> #define SER_IP "192.168.125.254" //服務器端IP #define SER_PORT 8888 //服務器端端口號#define CLI_IP "192.168.199.131" //客戶端IP #define CLI_P…

100條數據秒殺,如何避免超賣【待補充更細的資料】

使用Redis預減庫存&#xff1a;利用Redis的原子性操作&#xff0c;如DECR命令&#xff0c;來預先減少庫存。當商品庫存數量在Redis中被減少到0時&#xff0c;后續的請求將被拒絕&#xff0c;從而確保只有限定數量的訂單能夠進入后續流程。悲觀鎖&#xff1a;在數據庫層面使用悲…

面試筆記系列八之JVM基礎知識點整理及常見面試題

目錄 類實例化加載順序 類的實例化順序 JVM創建對象的過程 JVM的運行機制 直接內存&#xff08;Direct Memory&#xff09; JVM后臺運行的線程 JVM 常用參數 標準參數中比較有用的&#xff1a; 非標準參數又稱為擴展參數&#xff0c;比較有用的是 非Stable參數 class初…

【DAY07 軟考中級備考筆記】數據結構:線性結構,數組矩陣和廣義表

數據結構&#xff1a;線性結構&#xff0c;數組矩陣和廣義表 3月2日 – 天氣&#xff1a;晴 1. 線性表的定義和存儲方式 > 這一部分只需要掌握下面的兩點即可&#xff1a; > > * 采用順序存儲和鏈式存儲的特點 > * 單鏈表的插入和刪除操作 2. 棧和隊列 > 這里需…

35 Spring整合Elasticsearch

文章目錄 Spring整合Elasticsearch引入依賴配置Elasticsearch解決沖突 使用ElasticsearchSpring Data Elasticsearch建立映射關系常用方法添加數據修改數據刪除數據搜索數據&#xff08;es核心&#xff09;步驟構造搜索條件 并 應用進行查詢使用查詢結果 Spring整合Elasticsear…

Spring注解之事務 @Transactional

目錄 Spring 對事務的支持 事務 Transactional Spring 對事務的支持 提醒一次&#xff1a;你的程序是否支持事務首先取決于數據庫 &#xff0c;比如使用 MySQL 的話&#xff0c;如果你選擇的是 innodb 引擎&#xff0c;那么恭喜你&#xff0c;是可以支持事務的。但是&#x…

鴻蒙Harmony應用開發—ArkTS聲明式開發(通用屬性:Popup控制)

給組件綁定popup彈窗&#xff0c;并設置彈窗內容&#xff0c;交互邏輯和顯示狀態。 說明&#xff1a; 從API Version 7開始支持。后續版本如有新增內容&#xff0c;則采用上角標單獨標記該內容的起始版本。 popup彈窗的顯示狀態在onStateChange事件回調中反饋&#xff0c;其顯…

opencv內存溢出del釋放變量 (python)

報錯&#xff1a; cv2.error: OpenCV(3.4.17) D:\a\opencv-python\opencv-python\opencv\modules\core\src\alloc.cpp:73: error: (-4:Insufficient memory) Failed to allocate 12211548 bytes in function ‘cv::OutOfMemoryError’ 檢查內存代碼 import psutil# 獲取當前進…

內存空間擔保機制

什么是內存空間擔保機制&#xff1f; 內存空間擔保機制&#xff08;Memory Space Guarantee&#xff09;是垃圾回收&#xff08;Garbage Collection&#xff09;算法中的一種策略。它用于在進行垃圾回收過程&#xff08;如Minor GC或Full GC&#xff09;時&#xff0c;確保老年…

Java項目layui分頁中文亂碼

【問題描述】這部分沒改之前中文亂碼。 【解決辦法】在layui.js或者layui.all.js文件中替換共、頁、條轉換成Unicode碼格式。 字符Unicode共&#x5171頁&#x9875條&#x6761【完美解決】改完之后重新運行項目&#xff0c;瀏覽器F12緩存清除就好了&#xff0c;右鍵

MySQL的單表和多表查詢

我們在前面曾構建過三個用于實驗的表格&#xff0c;下面將基于這三個表進行實踐。 # 建立一個用于實驗的三個表格 mysql> create table emp (-> empno varchar(10),-> ename varchar(50),-> job varchar(50),-> mgr int,-> hiredate timestamp,-&…

課程表系列(BFS)

廣度優先搜索 文章目錄 廣度優先搜索207. 課程表210. 課程表 II思路 630. 課程表 III1462. 課程表 IV547. 省份數量 207. 課程表 207. 課程表 你這個學期必須選修 numCourses 門課程&#xff0c;記為 0 到 numCourses - 1 。 在選修某些課程之前需要一些先修課程。 先修課程…

c++11 標準模板(STL)(std::tuple)(三)

定義于頭文件 <tuple> template< class... Types > class tuple; (C11 起) 類模板 std::tuple 是固定大小的異類值匯集。它是 std::pair 的推廣。 若 (std::is_trivially_destructible_v<Types> && ...) 為 true &#xff0c;則 tuple 的析構函數是…

【AI繪畫】免費GPU Tesla A100 32G算力部署Stable Diffusion

免責聲明 在閱讀和實踐本文提供的內容之前&#xff0c;請注意以下免責聲明&#xff1a; 侵權問題: 本文提供的信息僅供學習參考&#xff0c;不用做任何商業用途&#xff0c;如造成侵權&#xff0c;請私信我&#xff0c;我會立即刪除&#xff0c;作者不對讀者因使用本文所述方法…

Matlab 機器人工具箱 RobotArm類

文章目錄 1 RobotArm1.1 方法1.2 注意2 RobotArm.RobotArm3 RobotArm.cmove4 其他官網:Robotics Toolbox - Peter Corke 1 RobotArm 串聯機械臂類 1.1 方法 方法描述plot顯示機器人的圖形表示teach驅動物理和圖形機器人mirror使用機器人作為從機來驅動圖形</

深入了解Kafka的文件存儲原理

Kafka簡介 Kafka最初由Linkedin公司開發的分布式、分區的、多副本的、多訂閱者的消息系統。它提供了類似于JMS的特性&#xff0c;但是在設計實現上完全不同&#xff0c;此外它并不是JMS規范的實現。kafka對消息保存是根據Topic進行歸類&#xff0c;發送消息者稱為Producer&…

IntelliJ IDEA 常用的插件

IntelliJ IDEA有很多常用的插件&#xff0c;這些插件可以擴展IDE的功能&#xff0c;提高開發效率。以下是一些常用的插件&#xff1a; Maven Helper&#xff1a;這是一款分析Maven依賴沖突的插件。在沒有此插件時&#xff0c;查看Maven的依賴樹和檢查依賴包沖突可能需要輸入命…