2009-12-12 313 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容器首先调用构造函数,然后在构造的对象上设置一个属性。通常这用于可选的依赖项,例如记录器。但是你也可以用它来打破两个需要对方的类之间的循环依赖关系。

这是不漂亮,虽然,我肯定会推荐你的重构类,以消除循环依赖。