2009-12-12 268 views
8

對於依賴注入來說很新穎,我試圖弄清楚這是否是反模式。使用依賴注入來注入依賴注入器

比方說,我有3個組件:

Foo.Shared - this has all the interfaces 
Foo.Users - references Foo.Shared 
Foo.Payment - references Foo.Shared 

Foo.Users需要是內Foo.Payment建立了一個對象,並Foo.Payment還需要從Foo.Users東西。這會產生某種循環依賴。

我已經在Foo.Shared中定義了一個代理我使用的依賴注入框架(在這種情況下爲NInject)的接口。

public interface IDependencyResolver 
{ 
    T Get<T>(); 
} 

在容器應用程序,我有這個接口的實現:

public class DependencyResolver:IDependencyResolver 
{ 
    private readonly IKernel _kernel; 

    public DependencyResolver(IKernel kernel) 
    { 
     _kernel = kernel; 
    } 

    public T Get<T>() 
    { 
     return _kernel.Get<T>(); 
    } 
} 

的配置是這樣的:

public class MyModule:StandardModule 
{ 
    public override void Load() 
    { 
     Bind<IDependencyResolver>().To<DependencyResolver>().WithArgument("kernel", Kernel); 
     Bind<Foo.Shared.ISomeType>().To<Foo.Payment.SomeType>(); // <- binding to different assembly 
     ... 
    } 
} 

這讓我來實例化的Foo.Payment.SomeType一個新對象來自Foo.Users內部,無需直接參考:

public class UserAccounts:IUserAccounts 
{ 
    private ISomeType _someType; 
    public UserAccounts(IDependencyResolver dependencyResolver) 
    { 
     _someType = dependencyResolver.Get<ISomeType>(); // <- this essentially creates a new instance of Foo.Payment.SomeType 
    } 
} 

這使得它不清楚UserAccounts類的確切依賴關係在這種情況下,這讓我覺得這不是一個好習慣。

我該怎麼做到這一點?

有什麼想法?

+1

+1只是繞口令稱號。 – womp 2009-12-12 01:01:41

+0

同樣在這裏,我愛的標題:) – 2009-12-12 01:06:52

回答

7

Althought有點爭議:是的,這是一個反模式。它被稱爲服務定位器,雖然有些人認爲它是一個合適的設計模式,但我認爲它是一種反模式。

這個問題的是例如使用您UserAccounts類成爲隱含而不是明確。雖然構造函數聲明它需要一個IDependencyResolver,但它沒有說明它應該包含哪些內容。如果你傳遞一個無法解析ISomeType的IDependencyResolver,它會拋出。

更糟糕的是,在以後的迭代中,您可能會試圖從UserAccounts內部解析其他類型的。它會編譯得很好,但如果/無法解析類型,可能會在運行時拋出。

不要走這條路。

從給出的信息中,不可能完全告訴你如何解決循環依賴關係中的特定問題,但我建議你重新考慮一下你的設計。在許多情況下,循環引用漏抽象的症狀,所以也許如果您改造您的API了一下,它就會消失 - 這是常常令人驚訝的小有需要更改。

一般來說,解決任何問題是增加的間接的另一層。如果您確實需要從兩個庫合作的對象中緊密合作,則通常可以引入中間代理。

  • 在許多情況下,發佈/訂閱模型效果很好。
  • The 調解員模式可能會提供一個替代方案,如果通訊必須兩種方式。
  • 您也可以引入抽象工廠來檢索,而不是要求它立即連線了你需要,你需要它的實例。
+0

這就是我認爲的依賴關係不明確,所以它必須是一個反模式。 :)抽象工廠是我的第一選擇,但它使代碼過於複雜。在真正的應用程序中,我需要創建大量不同的類型,而不僅僅是一個。我要麼硬編碼每個不同的工廠方法,要麼使用泛型將接口關聯到具體類(也是硬編碼)。但我失去了依賴注入框架的這樣的力量,它會變得非常凌亂配置綁定,甚至需要人工的依賴注入/類型解析代碼使用反射點。 – andreialecu 2009-12-12 11:15:06

+0

總有支付:)我不同意,它變得凌亂配置容器的代價 - 它可能會變得非常囉嗦冗長,但它會包括很多,幾乎聲明代碼,這是一個的優秀的折衷。大部分你可能能夠使用基於約定的配置解決的冗長 - 尤其是如果你最終得到許多類似的抽象工廠,必須以相同的方式進行配置。 Castle Windsor具有的功能使您能夠按照慣例在幾個簡單的語句中進行配置 - 我不知道NInject是否也可以這樣做... – 2009-12-12 15:24:39

1

這對我來說似乎有點奇怪。是否有可能將需要兩個引用的邏輯分成第三個程序集以打破依賴關係並避免風險?

2

我同意ForeverDebugging - 這將是很好的消除循環依賴。看看你是否能分出類是這樣的:

  • Foo.Payment.dll:的類只處理支付,而不是與用戶
  • Foo.Users.dll:只有與用戶打交道的類,不支付
  • Foo.UserPayment.dll:類是既支付處理和用戶

然後你有一個組件引用兩個人,但沒有相關的圈子。

如果您在程序集之間有循環依賴關係,它並不一定意味着您在類之間有循環依賴關係。例如,假設你有這些依賴關係:

  • Foo.Users.UserAccounts取決於Foo.Shared.IPaymentHistory,這是由Foo.Payment.PaymentHistory實現。
  • 不同的付款類Foo.Payment.PaymentGateway取決於Foo.Shared.IUserAccounts。 IUserAccounts由Foo.Users.UserAccounts實現。

假設沒有其他依賴關係。

這裏有一個程序集的循環,它們將在運行時依賴於你的應用程序(儘管它們在編譯時並不相互依賴,因爲它們通過共享DLL)。但是在編譯時或運行時沒有相互依賴的類的循環。

在這種情況下,您仍然應該能夠正常使用您的IoC容器,而無需添加額外的間接級別。在你的MyModule中,只需將每個接口綁定到適當的具體類型即可。讓每個類接受它的依賴作爲構造函數的參數。當您的頂級應用程序代碼需要一個類的實例時,讓它向IoC容器詢問該類。讓IoC容器擔心找到課程依賴的所有東西。



如果你做的類之間的循環依賴結束了,你可能需要在一個類使用屬性注入(又名setter注入),而不是構造函數注入。我不使用Ninject,但它支持屬性注入 - here is the documentation

通常IoC容器使用構造函數注入 - 它們將依賴關係傳遞給依賴它們的類的構造函數。但是,當存在循環依賴時,這不起作用。如果類A和類B相互依賴,則需要將類A的實例傳遞給類B的構造函數。但爲了創建A,需要將類B的實例傳遞給其構造函數。這是一個雞與雞蛋的問題。

通過屬性注入,您可以告訴IoC容器首先調用構造函數,然後在構造的對象上設置一個屬性。通常這用於可選的依賴項,例如記錄器。但是你也可以用它來打破兩個需要對方的類之間的循環依賴關係。

這是不漂亮,雖然,我肯定會推薦你的重構類,以消除循環依賴。