依賴倒置

開放

9.1 依賴倒置

請記住,我們曾經說過,在服務器應用程序中,您不能只通過創建流new Thread().start()?只有容器應該創建線程。我們現在將進一步發展這個想法。

所有對像也應該只由容器創建。當然,我們不是在談論所有對象,而是在談論所謂的業務對象。它們通常也被稱為垃圾箱。這種方法的支柱源於 SOLID 的第五條原則,該原則要求擺脫類並轉向接口:

  • 頂級模塊不應該依賴於低級模塊。那些和其他人都應該依賴於抽象。
  • 抽像不應依賴於細節。實現必須依賴於抽象。

模塊不應該包含對特定實現的引用,它們之間的所有依賴關係和交互都應該完全建立在抽象(即接口)的基礎上。這條規則的精髓可以用一句話來表達:所有的依賴關係都必須是接口的形式

儘管它的基本性質和表面上的簡單性,這條規則最常被違反。即,每次我們在程序/模塊的代碼中使用new操作符,創建一個特定類型的新對象時,就不再依賴於接口,而是形成了對實現的依賴。

很明顯,這是無法避免的,必須在某處創建對象。但是,至少,您需要盡量減少執行此操作的地方以及明確指定類的地方的數量,並對這些地方進行本地化和隔離,以免它們分散在整個程序代碼中。

一個很好的解決方案是在專門的對象和模塊中集中創建新對象的瘋狂想法——工廠、服務定位器、IoC 容器。

從某種意義上說,這樣的決定遵循單一選擇原則,即:“當軟件系統必須支持多種選擇時,它們的完整列表應該只為系統的一個模塊所知”

因此,如果將來有必要添加新的選項(或新的實現,就像我們正在考慮的創建新對象的情況一樣),那麼只更新包含此信息的模塊就足夠了,而所有其他模塊將保持不受影響,並將能夠像往常一樣繼續他們的工作。

示例 1

JDK 為您提供葉的正確實現而不是new ArrayList 編寫類似的東西是有意義的:ArrayList、LinkedList,甚至是 ConcurrentList。List.new()

例如,編譯器發現有來自不同線程的對對象的調用,並在那裡放置一個線程安全的實現。或者sheet中間insert過多,則基於LinkedList實現。

示例 2

例如,這已經發生在各種情況下。您上次編寫排序算法來對集合進行排序是什麼時候?相反,現在大家都使用方法Collections.sort(),集合的元素必須支持Comparable接口(comparable)。

如果sort()你將少於 10 個元素的集合傳遞給該方法,則很有可能使用冒泡排序(Bubble sort)對其進行排序,而不是 Quicksort。

示例 3

編譯器已經在觀察您如何連接字符串並將您的代碼替換為StringBuilder.append().

9.2 實踐中的依賴倒置

現在最有趣的是:讓我們考慮一下如何將理論與實踐結合起來。模塊如何正確地創建和接收它們的“依賴關係”而不違反依賴倒置?

為此,在設計模塊時,您必須自己決定:

  • 模塊做什麼,執行什麼功能;
  • 然後模塊需要從它的環境中獲取,也就是說,它必須處理哪些對象/模塊;
  • 他將如何得到它?

為了遵守依賴倒置的原則,您肯定需要決定您的模塊使用哪些外部對像以及它將如何獲取對它們的引用。

這裡有以下選項:

  • 模塊本身創建對象;
  • 模塊從容器中獲取對象;
  • 該模塊不知道對象來自哪裡。

問題在於創建對象需要調用特定類型的構造函數,這樣一來,模塊將不依賴於接口,而是依賴於具體的實現。但是如果我們不想在模塊代碼中顯式地創建對象,那麼我們可以使用工廠方法模式

“最重要的是,我們不是通過 new 直接實例化一個對象,而是為客戶端類提供一些接口來創建對象。由於這樣的接口總是可以被正確的設計覆蓋,我們在使用低級模塊時獲得了一些靈活性在高級模塊中”

在需要創建相關對象組或系列的情況下,使用抽象工廠代替工廠方法

9.3 使用服務定位器

該模塊從已經擁有它們的人那裡獲取必要的對象。假設系統有一些對象存儲庫,模塊可以在其中“放置”它們的對象並從存儲庫中“取出”對象。

這種方法是通過服務定位器模式實現的其主要思想是程序有一個對象,它知道如何獲取所有可能需要的依賴項(服務)。

與工廠的主要區別在於服務定位器不創建對象,但實際上已經包含實例化對象(或者知道在哪裡/如何獲取它們,如果它創建,那麼在第一次調用時只創建一次)。每次調用時,工廠都會創建一個新對象,您擁有該對象的全部所有權,您可以隨心所欲地使用它。

重要服務定位器生成對相同的已存在對象的引用。因此,您需要非常小心服務定位器發布的對象,因為其他人可以與您同時使用它們。

Service Locator 中的對象可以直接通過配置文件來添加,確實以任何方便程序員的方式添加。服務定位器本身可以是具有一組靜態方法的靜態類、單例或接口,並且可以通過構造函數或方法傳遞給所需的類。

服務定位器有時被稱為反模式並且不被鼓勵(因為它創建隱式連接並且只給出良好設計的外觀)。您可以從 Mark Seaman 那裡了解更多信息:

9.4 依賴注入

該模塊根本不關心“挖掘”依賴項。它只決定它需要做什麼,而所有必要的依賴項都是由其他人從外部提供(引入)的。

這就是所謂的 -依賴注入。通常,所需的依賴項作為構造函數參數(構造函數注入)或通過類方法(Setter 注入)傳遞。

這種方法顛倒了創建依賴項的過程——而不是模塊本身,依賴項的創建是由外部人員控制的。來自對象主動發射器的模塊變為被動 - 不是他創造,而是其他人為他創造。

這種方向的改變被稱為控制反轉,或好萊塢原則——“不要打電話給我們,我們會打電話給你。”

這是最靈活的解決方案,給了模塊最大的自主權。可以說,只有它完全貫徹了“單一職責原則”——模塊應該完全專注於做好它的工作,而不用擔心其他任何事情。

為模塊提供工作所需的一切是一個單獨的任務,應該由適當的“專家”處理(通常是某個容器,一個 IoC 容器,負責管理依賴項及其實現)。

其實這裡的一切都和生活一樣:組織嚴密的公司裡,程序員編程,辦公桌、電腦等工作所需的一切都是由辦公室經理購買和提供的。或者,如果您使用程序作為構造函數的比喻,那麼模塊不應該考慮電線,其他人參與組裝構造函數,而不是零件本身。

不誇張地說,使用接口來描述模塊之間的依賴關係(Dependency Inversion)+這些依賴關係的正確創建和注入(主要是Dependency Injection)是解耦的關鍵技術

它們是代碼鬆散耦合、靈活性、抗更改性、重用性的基礎,沒有它們,所有其他技術都毫無意義。這是鬆散耦合和良好架構的基礎。

Martin Fowler 詳細討論了控制反轉的原理(連同依賴注入和服務定位器)。他的兩篇文章都有翻譯:“Inversion of Control Containers and the Dependency Injection pattern”“Inversion of Control”

留言
  • 受歡迎
你必須登入才能留言
此頁面尚無留言