簡體   English   中英

我應該避免使用依賴注入和IoC嗎?

[英]Should I avoid using Dependency Injection and IoC?

在我的中型項目中,我使用靜態類來存儲庫,服務等。它實際上工作得非常好,即使大多數程序員都期望相反。 我的代碼庫非常緊湊,干凈且易於理解。 現在我嘗試重寫所有內容並使用IoC (Invertion of Control),我非常失望。 我必須手動初始化每個類,控制器等中的十幾個依賴項,為接口添加更多項目等等。 我真的沒有看到我的項目有任何好處,似乎它導致的問題多於解決問題。 我在IoC / DI發現了以下缺點:

  • 更大的代碼化
  • 餛飩代碼而不是意大利面條代碼
  • 性能較慢,需要初始化構造函數中的所有依賴項,即使我要調用的方法只有一個依賴項
  • 沒有使用IDE時更難理解
  • 一些錯誤被推送到運行時
  • 添加額外的依賴(DI框架本身)
  • 新員工必須首先學習DI才能使用它
  • 很多樣板代碼,對創意人員不利(例如從構造函數復制實例到屬性......)

我們不測試整個代碼庫,只測試某些方法並使用真實數據庫。 那么,如果不需要模擬測試,是否應該避免依賴注入?

您的大部分擔憂似乎歸結為誤用或誤解。

  • 更大的代碼化

    這通常是適當尊重單一責任原則和界面隔離原則的結果。 它是否大得多? 我懷疑沒有你聲稱的那么大。 然而,它正在做的事情很可能是將類分解為特定的功能,而不是擁有可以做任何事情的“全能”類。 在大多數情況下,這是關注點健康分離的標志,而不是問題。

  • 餛飩代碼而不是意大利面條代碼

    再次,這很可能導致您在堆棧中思考而不是難以看到的依賴關系。 我認為這是一個很大的好處,因為它導致適當的抽象和封裝。

  • 性能較慢只需使用快速容器即可。 我最喜歡的是SimpleInjector和LightInject。

  • 需要初始化構造函數中的所有依賴項,即使我要調用的方法只有一個依賴項

    再一次,這表明您違反了單一責任原則。 這是一件好事,因為它迫使你在邏輯上思考你的架構,而不是添加willy-nilly。

  • 在沒有使用IDE時更難理解某些錯誤被推送到運行時

    如果你仍然沒有使用IDE,那么你會感到羞恥。 現代機器對它沒有好的爭論。 此外,如果您願意,某些容器(SimpleInjector)將在首次運行時進行驗證。 您可以通過簡單的單元測試輕松檢測到這一點。

  • 添加額外的依賴(DI框架本身)

    你必須選擇你的戰斗。 如果學習新框架的成本低於維護意大利面條代碼的成本(我懷疑它會是這樣),那么成本是合理的。

  • 新員工必須首先學習DI才能使用它

    如果我們回避新的模式,我們永遠不會成長。 我認為這是一個豐富和發展團隊的機會,而不是一種傷害他們的方式。 此外,權衡是學習意大利面條代碼,這可能比采用行業范圍的模式困難得多。

  • 很多樣板代碼對創意人員不利(例如從構造函數復制實例到屬性......)

    這是完全錯誤的。 應始終通過構造函數傳遞強制依賴項。 只應通過屬性設置可選的依賴項,並且只應在非常特定的情況下進行,因為它通常違反單一責任原則。

  • 我們不測試整個代碼庫,只測試某些方法並使用真實數據庫。 那么,如果不需要模擬測試,是否應該避免依賴注入?

    我認為這可能是對所有人最大的誤解。 依賴注入不僅僅是為了使測試更容易。 這樣你就可以瀏覽一下類構造函數的簽名,並立即知道使該類打勾所需的內容。 靜態類是不可能的,因為類可以在沒有押韻或理由的情況下隨時調用堆棧上下。 您的目標應該是為代碼添加一致性,清晰度和區別。 這是使用DI的最大原因,這也是我強烈建議您重溫它的原因。

雖然IoC / DI並不是適用於所有情況的銀彈,但您可能沒有正確應用它。 依賴注入背后的原則需要時間來掌握,或者至少它確實對我有用。 如果應用得當,它可以帶來(以及其他)以下好處:

  • 提高可測試性
  • 提高靈活性
  • 提高了可維護性
  • 改進了並行開發

從您的問題,我已經可以提取一些可能在您的情況下出錯的事情:

我必須在每個類中手動初始化十幾個依賴項

這意味着您創建的每個類都負責創建它所需的依賴項。 這是一種稱為Control Freak的反模式。 一個類不應該new它的依賴本身。 您甚至可以通過調用容器(或表示容器的抽象)來獲取特定依賴項,從而應用Service Locator反模式 ,其中您的類請求其依賴項。 類應該只定義它作為構造函數參數所需的依賴項。

十幾個依賴

該陳述暗示您違反了單一責任原則 這實際上沒有與IoC / DI耦合,您的舊代碼可能已經違反了單一責任原則,導致其變得難以理解和維護其他開發人員。 原作者通常很難理解為什么其他人很難維護代碼,因為你寫的東西經常很適合你。 通常,違反SRP會導致其他人無法理解和維護代碼。 違反SRP的測試類通常更難。 一個類最多應該有六個依賴項。

為界面添加更多項目等

這意味着您違反了重用抽象原則 通常,應用程序中的大多數組件/類應該包含在十幾個抽象中。 例如,實現某些用例的所有類可能都需要一個(通用)抽象 實現查詢的類也應該有一個抽象 對於我編寫的系統,80%到95%的組件(包含應用程序行為的類)被5到12個(通常是通用的)抽象所覆蓋。 大多數情況下,您不需要僅為接口創建新項目。 大多數時候,我將這些接口放在同一個項目的根目錄中。

更大的代碼化

您編寫的代碼量最初不會有很大差異。 然而,依賴注入的實踐僅在應用SOLID時才有效,並且SOLID會促進小型焦點類。 有一個責任的班級。 這意味着您將擁有許多易於理解且易於組成靈活系統的小類。 不要忘記:我們不應該努力編寫更少的代碼,而是更易於維護的代碼。

然而,憑借良好的SOLID設計和正確的抽象,我實際上不得不編寫比以前少得多的代碼。 例如,通過在應用程序的基礎結構層中編寫幾行代碼,而不是將其分散到整個應用程序中,可以應用某些跨領域問題(如日志記錄,審計跟蹤,授權等)。應用。 它甚至讓我能夠做以前不可行的事情,因為它們迫使我在整個代碼庫中做出徹底的改變,這是非常耗時的,管理層不允許我這樣做。

當沒有使用IDE時,餛飩代碼而不是意大利面代碼更難以理解

這是真的。 依賴注入促使類彼此分離。 這有時會使瀏覽代碼庫變得更加困難,因為類通常依賴於抽象而不是具體的類。 在過去,我發現DI給我的靈活性超過了到目前為止找到實現的成本。 使用Visual Studio 2015,我可以簡單地使用CTRL + F12來查找接口的實現。 如果只有一個實現,Visual Studio將直接跳轉到該實現。

性能較慢

這不是真的。 性能不必與僅使用靜態方法調用的代碼庫進行任何不同。 然而,您選擇讓您的課程具有瞬態生活方式,這意味着您可以在整個地方實現新的實例。 在我的上一個應用程序中,我為每個應用程序創建了一次所有類,它提供了與僅具有靜態方法調用大致相同的性能,但是應用程序的優點是非常靈活和可維護。 但請注意,即使您決定為每個(Web)請求創建new完整對象圖,性能成本也很可能比您在執行期間執行的任何I / O(數據庫,文件系統和Web服務調用)低幾個數量級。該請求,即使是最慢的DI容器。

一些錯誤被推送到運行時添加額外的依賴(DI框架本身)

這些問題都意味着使用DI庫。 DI庫在運行時進行對象組合。 但是,在執行依賴注入時,DI庫不是必需的工具。 在沒有工具的情況下使用依賴注入可以使小應用程序受益; 一種叫做Pure DI的做法。 您的應用程序可能無法從使用DI容器中受益,但大多數應用程序實際上都可以從使用依賴注入(正確使用時)中獲益。 Againt:工具是可選的,編寫可維護的代碼不是。

但即使您使用DI庫,也有內置工具的庫允許您驗證和診斷配置。 它們不會為您提供編譯時支持,但它們允許您在應用程序啟動或使用單元測試時運行此分析。 這可以防止您對整個應用程序進行回歸,以驗證您的容器是否正確連接。 我的建議是選擇一個DI容器來幫助您檢測這些配置錯誤。

新員工必須首先學習DI才能使用它

這是真的,但依賴注入本身實際上並不難學。 實際上很難學到的是正確應用SOLID原則,當您想要編寫需要由多個開發人員維護一段時間的應用程序時,無論如何都需要學習這一點。 我寧願投資教我的團隊中的開發人員編寫SOLID代碼而不是讓他們編寫代碼; 這肯定會導致以后的維護。

很多樣板代碼

當我們查看用C#6編寫的代碼時,有一些樣板代碼,但實際上並不是那么糟糕,特別是當你考慮它給出的優點時。 C#的未來版本將刪除樣板文件,這主要是由必須定義構造函數引起的,這些構造函數接受空值檢查並分配給私有變量的參數。 當引入記錄類型和非可空引用類型時,C#7或8肯定會解決這個問題。

這對創意人士不利

對不起,但這個論點簡直就是胡說八道。 我已經看到這個論點一遍又一遍地被用作編寫不良代碼的借口,開發人員不想學習設計模式和軟件原理和實踐。 創造性並不是編寫其他人無法理解的代碼或無法測試的代碼的借口。 我們需要應用可接受的模式和實踐,並且在該邊界內有足夠的空間來創造性,同時編寫好的代碼。 編寫代碼不是一門藝術; 這是一種手藝。

就像我說的那樣,DI在所有情況下都不合適,並且圍繞它的實踐需要時間來掌握。 我建議你閱讀Mark Seemann撰寫的“ .NET中的依賴注入 ”一書; 它會給出很多答案,並會讓你很好地理解如何以及何時應用它,何時不能。

有很多“教科書”的論據支持使用IoC,但根據我的個人經驗,收益是:

可以僅測試項目的某些部分,並模擬其他部分 例如,如果您有一個組件從DB返回配置,那么很容易模擬它,以便您的測試可以在沒有真正的數據庫的情況下工作。 對於靜態類,這是不可能的。

更好地查看和控制依賴關系 使用靜態類,很容易添加一些依賴,甚至沒有注意到,這可能會在以后產生問題。 使用IoC,這更明確,更明顯。

更明確的初始化順序 對於靜態類,這通常是黑盒子,並且由於循環使用可能存在潛在問題。

對我來說唯一的不便是,通過將所有內容放在接口之前,無法直接從使用中導航到實現(F12)。

但是,項目的開發人員可以在特定情況下判斷最佳利弊。

您有沒有選擇使用IOC庫(StructureMap,Ninject,Autofac等)的原因? 使用其中任何一項都會讓您的生活更輕松。

盡管David L已經對你的觀點做了很好的評論,但我也會添加自己的評論。

更大的代碼化

我不確定你是如何結束更大的代碼庫的; IOC庫的典型設置非常小,因為您在類構造函數中定義了不變量(依賴項),所以您還刪除了一些您不需要的代碼(即“新的xyz()”)。更多。

Ravioli代碼而不是意大利面條代碼

我恰巧喜歡餛飩:)

性能較慢,需要初始化構造函數中的所有依賴項,即使我要調用的方法只有一個依賴項

如果你這樣做那么你根本就沒有真正使用依賴注入。 您應該通過類本身的構造函數參數中聲明的依賴項參數接收現成的,完全加載的對象圖 - 而不是在構造函數中創建它們! 大多數現代IOC庫都非常快,而且永遠不會成為性能問題。 這是一個很好的視頻,證明了這一點。

何時不使用IDE會更難理解

這是真的,但這也意味着你可以借此機會從抽象的角度思考。 例如,您可以查看一段代碼

public class Something
{
    readonly IFrobber _frobber;
    public Something(IFrobber frobber)
    {
        _frobber=frobber;
    }

    public void LetsFrobSomething(Thing theThing)
    {
        _frobber.Frob(theThing)
    }
}

當您查看此代碼並嘗試確定它是否有效,或者它是否是問題的根本原因時,您可以忽略實際的IFrobber實現; 它只是代表了抽象能力FROB東西,你不需要沿着任何特定Frobber會如何做它的工作精神進行。 你可以專注於確保這個類能夠完成它應該做的事情 - 即將一些工作委托給某種類型的Frobber。

另請注意,您甚至不需要在此處使用接口; 你可以繼續並注入具體的實現。 然而,這傾向於違反依賴性倒置原則(這與我們在這里談論的DI只是坦率相關)因為它迫使類依賴於結構而不是抽象。

一些錯誤被推送到運行時

不會比在構造函數中手動構建圖形更多或更少;

添加額外的依賴(DI框架本身)

這也是事實,但是大多數IOC庫非常小且不引人注目,在某些時候你必須決定是否有一個稍微大一點的生產工件的權衡(實際上是這樣)

新員工必須首先學習DI才能使用它

這與任何新技術的情況並沒有什么不同:)學習使用IOC庫往往會打開其他可能性,如TDD,SOLID原則等等,這絕不是壞事!

很多樣板代碼,對創意人員不利(例如從構造函數復制實例到屬性......)

我不明白這個,你怎么可能得到很多樣板代碼; 我不計算將給定的依賴項存儲在私有只讀成員中作為值得討論的樣板 - 請記住,如果每個類有超過3或4個依賴項,則可能違反SRP並應重新考慮您的設計。

最后,如果您不相信這里提出的任何論點,我仍然建議您閱讀Mark Seeman的“ .Net中依賴注入 ”。 (或者他可以在他的博客上找到關於DI的任何其他內容)。 我保證你會學到一些有用的東西,我可以告訴你,它改變了我編寫軟件的方式。

警告:我討厭IoC。

這里有很多很棒的答案令人欣慰。 根據史蒂文(非常強烈的答案)的主要好處是:

  • 提高可測試性
  • 提高靈活性
  • 提高了可維護性
  • 改進了可擴展性

我的經歷非常不同,這里有一些平衡:

(Bonus)愚蠢的存儲庫模式

通常,這與IoC一起包含在內。 存儲庫模式應僅用於訪問外部數據,並且可互換性是核心期望。

當您使用此實體框架時,您將禁用實體框架的所有功能,這也適用於服務層。

例如。 呼叫:

var employees = peopleService.GetPeople(false, false, true, true); //Terrible

它應該是:

var employees = db.People.ActiveOnly().ToViewModel();

在這種情況下使用擴展方法。

誰需要靈活性?

如果您沒有計划更改服務實現,則不需要它。 如果您認為將來會有多個實現,那么可能會添加IoC,並且僅針對該部分。

但是“可測試性”!

實體框架(以及可能還有其他ORM)允許您將連接字符串更改為指向內存數據庫。 當然,這只能從EF7開始。 但是,它可以簡單地作為臨時環境中的新(適當)測試數據庫。

您是否有其他特殊測試資源和服務點? 在這個時代,它們可能是不同的WebService URI端點,也可以在App.Config / Web.Config中配置。

自動化測試使您的代碼可維護

TDD - 如果它是Web應用程序,請使用Jasmine或Selenium並進行自動行為測試。 這會一直測試用戶的所有內容。 這是一項長期投資,從覆蓋關鍵特性和功能開始。

DevOps / SysOps - 維護用於配置整個環境的腳本(這也是最佳實踐),啟動暫存環境並運行所有測試。 您還可以克隆生產環境並在那里運行測試。 不要將“可維護”和“可測試”作為選擇IoC的借口。 從這些要求開始,找到滿足這些要求的最佳方法。

可擴展性 - 以什么方式?

(我可能需要閱讀這本書)

  • 對於編碼器可擴展性,分布式代碼版本控制是常態(雖然我討厭合並)。
  • 對於人力資源可擴展性,您不應該浪費時間為項目設計額外的抽象層。
  • 對於生產並發用戶可伸縮性,您應該構建,測試,然后進行改進。
  • 對於服務器吞吐量可擴展性,您需要考慮比IoC更高級別。 你打算在客戶局域網上運行服務器嗎? 你能復制你的數據嗎? 您是在數據庫級別還是應用程序級別進行復制? 移動時離線訪問是否重要? 這些是實質性的架構問題,其中IoC很少是答案。

試試F12

如果您正在使用IDE(您應該這樣做),例如Visual Studio Community Edition,那么您將了解F12可以如何方便地瀏覽代碼。

使用IoC,您將被帶到接口,然后您需要使用特定接口查找所有引用。 只有一個額外的步驟,但對於一個如此使用的工具,它讓我感到沮喪。

史蒂文在球上

使用Visual Studio 2015,我可以簡單地使用CTRL + F12來查找接口的實現。

是的,但是你必須在兩個用法和聲明的列表中搜索。 (實際上我認為在最新的VS中,聲明單獨列出,但它仍然是一個額外的鼠標點擊,讓你的手離開鍵盤。我應該說這是Visual Studio的限制,不能帶你到一個接口實現直接。

如果你必須在代碼中手動初始化依賴項,那么你做錯了。 IoC一般模式是構造函數注入,或者可能是屬性注入。 類或控制器根本不應該知道DI容器。

通常,您所要做的就是:

  1. 配置容器,如Interface = Class in Singleton scope
  2. 使用它,如Controller(Interface interface) {}
  3. 受益於在一個地方控制所有依賴項

我沒有看到任何樣板代碼或更慢的性能或您描述的任何其他內容。 沒有它,我無法真正想象如何編寫更多或更少復雜的應用程序。

但一般來說,你需要決定什么更重要。 為了取悅“有創造力的人”或構建可維護且強大的應用程序。

順便說一句,要創建屬性或從構造函數歸檔,您可以在R#中使用Alt+Enter ,它可以為您完成所有工作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM