我对依赖注入非常陌生,我正在尝试弄清楚这是否是反模式。
假设我有3个程序集:
Foo.Shared - this has all the interfaces
Foo.Users - references Foo.Shared
Foo.Payment - references Foo.SharedFoo.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.Users内部实例化Foo.Payment.SomeType的新对象,而不需要直接引用:
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类的确切依赖项变得不清楚,这让我认为这不是一个好的实践。
我还能做些什么呢?
有什么想法吗?
发布于 2009-12-12 16:21:16
尽管有些争议:是的,这是一个反模式。它被称为服务定位器,虽然有些人认为它是一种适当的设计模式,但我认为它是一种反模式。
这个问题是UserAccounts类的用法变得隐式而不是显式。虽然构造函数声明它需要一个IDependencyResolver,但它并没有声明应该在其中包含什么内容。如果你传递给它一个不能解析ISomeType的IDependencyResolver,它将抛出异常。
更糟糕的是,在以后的迭代中,您可能会尝试从UserAccounts中解析其他类型。它将会编译得很好,但是如果类型不能被解析,它可能会在运行时抛出。
别走那条路。
从给出的信息来看,不可能确切地告诉您应该如何解决循环依赖的特定问题,但我建议您重新考虑您的设计。在许多情况下,循环引用是away 的症状,所以如果你稍微重塑一下你的应用程序接口,它就会消失--通常令人惊讶的是,需要进行如此小的更改。
一般而言,任何问题的解决方案都是添加另一层间接层。如果您确实需要两个库中的对象紧密协作,则通常可以引入一个中间代理。
在许多情况下,Publish/subscribe模型工作得很好。如果通信必须是双向的,
发布于 2009-12-12 11:25:21
我同意ForeverDebugging的观点--消除循环依赖会很好。看看你能不能像这样分离这些类:
只处理支付的
然后,您有一个引用了另外两个程序集的程序集,但没有依赖项的循环。
如果程序集之间确实存在循环依赖关系,则不一定意味着类之间存在循环依赖关系。例如,假设您有以下依赖关系:
实现IUserAccounts
假设没有其他依赖项。
这里有一圈程序集,它们在运行时将在应用程序中相互依赖(尽管它们在编译时不相互依赖,因为它们通过共享的DLL)。但在编译时或运行时,并不存在相互依赖的类的循环。
在这种情况下,您应该仍然能够正常使用IoC容器,而无需添加额外的间接层。在MyModule中,只需将每个接口绑定到适当的具体类型即可。使每个类接受其依赖项作为构造函数的参数。当您的顶级应用程序代码需要一个类的实例时,让它向IoC容器请求该类。让IoC容器负责查找该类所依赖的所有内容。
如果类之间确实存在循环依赖,则可能需要在其中一个类上使用属性注入(也称为setter注入),而不是使用构造函数注入。我不使用Ninject,但它确实支持属性注入- here is the documentation。
通常,IoC容器使用构造函数注入-它们将依赖项传递给依赖于它们的类的构造函数。但当存在循环依赖时,这就不起作用了。如果A类和B类相互依赖,则需要将A类的实例传递给B类的构造函数。但是,为了创建A,需要将B类的实例传递给它的构造函数。这是一个先有鸡后有蛋的问题。
通过属性注入,您可以告诉IoC容器首先调用构造函数,然后在构造的对象上设置一个属性。通常,这用于可选依赖项,例如记录器。但您也可以使用它来打破两个相互需要的类之间的循环依赖。
但这并不美观,我绝对建议重构您的类以消除循环依赖。
发布于 2009-12-12 10:26:07
这对我来说确实有点奇怪。有没有可能将需要两个引用的逻辑分离到第三个程序集中,以打破依赖关系并避免风险?
https://stackoverflow.com/questions/1891778
复制相似问题