2011-09-26 61 views
1

我一直在.NET中使用IoC(主要是Unity)和依賴注入,現在我真的很喜歡這種模式,以此來鼓勵創建軟件類耦合,應該更容易隔離進行測試。嘗試應用良好的依賴注入實踐時遇到的問題

我通常試圖堅持的方法是"Nikola's Five Laws of IoC" - 特別是不注入容器本身,只使用構造函數注入,以便可以清楚地看到類的構造函數簽名中的所有依賴關係。 Nikola確實有一個帳戶在這裏,但我不知道他是否仍然活躍。

無論如何,當我最終違反了其他法律之一,或者通常以某種感覺不到或者看起來不正確的事情結束時,我不得不質疑我是否錯過了某些東西,可以做得更好,還是僅僅是在某些情況下不應使用IoC。考慮到這一點這裏的這幾個例子,我會就這些任何指針或進一步討論感激:

  1. 類有太多的依賴。 (「任何具有超過3個依賴關係的類別都應該被質疑SRP違規」)。我知道這其中出現了很多依賴注入的問題,但看完這些後,我仍然沒有任何尤里卡時刻,解決我的問題:

    • 一)在一個大型的應用程序,我總是覺得我需要3依賴性只是爲了訪問基礎設施(示例 - 日誌記錄,配置,持久性),然後才能獲得該類所需的特定依賴關係,以便完成其(完成單個責任)作業。我意識到這種方法會重構並將這些依賴關係組合成一個整體,但我經常發現這只是其他幾個服務的一個表面,而不是真正承擔自己的責任。在這個規則的背景下,是否可以忽略某些基礎設施的依賴關係,只要這個班級被認爲仍然有單一的責任?

    • b)重構可能會增加這個問題。考慮拆分一個已經變得有點大的類的相當普遍的任務 - 將一個功能區域移動到一個新類中,並且第一個類變得依賴於它。假設第一個類仍然需要它以前的所有依賴,它現在有一個額外的依賴。在這種情況下,我可能不介意這種依賴關係更緊密地耦合在一起,但它仍然更容易讓容器提供它(與使用new(...)相反),即使沒有新的依賴關係,它也可以做到這一點它自己的界面。

    • c)在一個特定的例子中,我有一個類負責通過系統每隔幾分鐘運行各種不同的功能。由於所有的功能都屬於不同的領域,因此這個類最終只有許多依賴關係才能夠執行每個功能。我猜在這種情況下,可能涉及事件的其他方法應該被考慮,但到目前爲止我還沒有嘗試去做,因爲我想協調任務運行的順序,並且在某些情況下應用涉及結果的邏輯方式。

  2. 一旦我在一個應用程序中使用的IoC好像幾乎所有I類創建一個使用另一個類最終被註冊和/或由容器注入。這是預期的結果,還是某些類與IoC無關?在代碼中新增了一些東西的替代方法看起來像是一種代碼異味,因爲它緊密耦合。這也與上述1b有關。

  3. 我在應用程序啓動時完成了我的所有容器初始化,爲系統中的每個接口註冊類型。有些是故意單一實例生命週期,其他人可以在每次解決問題時成爲新實例。然而,由於後者是前者的依賴關係,實際上它們也成爲單一實例,因爲它們只在一次實施時才解決。在很多情況下,這並不重要,但是在某些情況下,我確實希望每次執行操作時都使用不同的實例,所以不能使用內置的容器功能,我不得不一個工廠依賴關係,所以我可以強制這種行爲或ii)通過容器,以便我可以解決每一次。在尼古拉的指導下,這兩種方法都令人不悅,但我認爲i)是兩種邪惡中較小的一種,我在某些情況下使用它。

+5

請嘗試一次提出一個問題。 – svick

+1

擁有3個以上的依賴是非常普遍的。對我而言,超過5個依賴是代碼味道。 – Steven

+0

是的,我現在意識到我應該分離出來,但是由於背景是相關的,所以我在寫作的時候就會被帶走。把它歸結爲缺乏經驗。現在不能重構我的問題,因爲我有一些答案... – zeroid

回答

3

在一個大型的應用程序,我總是覺得我需要3只依賴訪問基礎設施(例子 - 日誌,配置,持久性)

恕我直言,基礎設施不依賴。使用servicelocator獲取記錄器(private ILogger _logger = LogManager.GetLogger())我沒有問題。

但是,從我的觀點來看,持久性不是基礎設施。這是一個依賴。把你的課分成更小的部分。

重構可能會增加此問題。

當然。在成功重構所有類之前,您將獲得更多的依賴關係。只要掛在那裏,繼續重構。

在單獨的項目(分離的接口模式)中創建接口,而不是向類中添加依賴項。

在一個特定的例子中,我有一個類負責通過系統每隔幾分鐘運行各種不同的功能。由於所有的功能都屬於不同的領域,因此這個類最終只有許多依賴關係才能夠執行每個功能。

然後你採取了錯誤的方法。任務運行者不應該依賴於應該運行的所有任務,而應該是相反的方式。所有任務應該在跑步者中註冊。

一旦我在一個應用程序中使用的IoC好像幾乎所有I類創建一個使用另一個類最終被註冊和/或由容器注入。*

我在我的容器中註冊除業務對象,DTO等以外的所有東西。

我在應用程序啓動時完成了我的所有容器初始化工作,爲系統中的每個接口註冊類型。有些是故意單一實例生命週期,其他人可以在每次解決問題時成爲新實例。然而,由於後者是前者的依賴關係,實際上它們也成爲單一實例,因爲它們只在一次實施時才解決。

如果你能避免它,不要混合生命期。或者不要採取短暫的依賴關係。在這種情況下,您可以使用簡單的消息解決方案來更新單個實例。

您可能想要閱讀我的guidelines

+0

感謝您的指導,他們很有用。仍然在重構上掙扎 - 如果我分解了一個類,我經常發現所有新的類仍然具有相同的依賴關係,所以我所做的就是將更多的依賴添加到其他類,這些類需要我正在分解的類中的函數。我無法看到在這種持續重構的哪一點上,我會開始看到每個類的依賴性較少。至於任務跑步者,我知道你是對的,但我需要弄清楚如何維持對執行順序的控制。也可能這意味着每個有註冊任務的模塊都需要初始化? – zeroid

+0

添加一個示例類,我將爲您分解它,以便這些零件使用更少的依賴關係。 – jgauffin

1

讓我回答問題3.有一個單例取決於瞬態是容器分析器試圖檢測和警告的問題。 服務只應該依賴於其他服務的壽命大於或等於他們自己的壽命。注入一個工廠接口或委託來解決這個問題通常是一個很好的解決方案,並且傳入容器本身是一個糟糕的解決方案,因爲您最終得到了Service Locator anti-pattern

而不是注入工廠,你可以通過實現代理來解決這個問題。這裏有一個例子:

public interface ITransientDependency 
{ 
    void SomeAction(); 
} 

public class Implementation : ITransientDependency 
{ 
    public SomeAction() { ... } 
} 

根據這個定義,你可以基於該Composition Root定義代理類ITransientDependency

public class TransientDependencyProxy<T> : ITransientDependency 
    where T : ITransientDependency 
{ 
    private readonly UnityContainer container; 

    public TransientDependencyProxy(UnityContainer container) 
    { 
     this.container = container; 
    } 

    public SomeAction() 
    { 
     this.container.Resolve<T>().SomeAction(); 
    } 
} 

現在你可以註冊這個TransientDependencyProxy<T>爲單:

container.RegisterType<ITransientDependency, 
    TransientDependencyProxy<Implementation>>(
     new ContainerControlledLifetimeManager()); 

雖然它被註冊爲單例,它仍然會作爲一個瞬態,因爲它會將它的調用轉發給一個瞬態工具通貨膨脹。

通過這種方式,您可以完全隱藏ITransientDependency需要成爲來自應用程序其餘部分的暫態。

如果您需要針對多種不同服務類型的此行爲,那麼爲每個人和每個人定義代理將會非常麻煩。在這種情況下,你可以嘗試Unity的攔截功能。你可以定義一個單一的攔截器,允許你爲廣泛的服務類型執行此操作。

+0

Thankyou,我曾看到這在其他地方提到,但這是一個很好的解釋它如何工作。 – zeroid