问题描述
假设我有3个程序集:
Foo.Shared - 这有所有的界面
Foo.Users - 引用Foo.Shared
Foo.Payment - 引用Foo .Shared
Foo.Users需要一个内置于Foo.Payment中的对象,还有Foo.Payment需要从Foo.Users的东西。我创建了一些循环依赖关系。
我在Foo.Shared中定义了一个接口,代表了我使用的依赖注入框架(在这种情况下是NInject) / p>
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>()。到< DependencyResolver>()。WithArgument(kernel,Kernel);
绑定< Foo.Shared.ISomeType>()。到< Foo.Payment.SomeType>(); //< - 绑定到不同的程序集
...
}
}
这允许我从Foo.Users内部实例化一个新对象 Foo.Payment.SomeType
,而不需要直接引用:
public class UserAccounts:IUserAccounts
{
private ISomeType _someType;
public UserAccounts(IDependencyResolver dependencyResolver)
{
_someType = dependencyResolver.Get< ISomeType>(); //< - 这实际上创建了一个新的Foo.Payment.SomeType实例
}
}
这使得不清楚 UserAccounts
类的确切依赖关系在这个实例中,这使我认为这不是一个好习惯。
还有什么可以实现的?
任何想法?
虽然有些争议:是的,这是一种反模式。它被称为服务定位器,而有些人认为它是一个适当的设计模式,我认为它是一种反模式。
这个问题是使用例如您的UserAccounts类成为隐式,而不是显式。虽然构造函数指出它需要一个IDependencyResolver,但它并没有说明应该怎么做。如果你传递一个无法解析ISomeType的IDependencyResolver,它将会被抛出。
更糟的是,在稍后的迭代中,你可能会试图解决一些>其他类型从UserAccounts内。它将要编译的很好,但是如果/当类型无法解析时,可能会在运行时抛出。
不要去那个路由。 / p>
从所提供的信息中,无法直观地告诉您如何解决循环依赖关系中的特定问题,但我建议您重新思考设计。在许多情况下,循环引用是泄漏抽象的症状,所以也许如果你改写你的API一点,它会消失 - 通常令人惊讶的是需要多少小的改变。
一般来说,任何问题的解决方案是添加另一层间接。如果您确实需要两个图书馆的对象紧密协作,您通常可以介绍一个中间代理。
- 在许多情况下,
- Mediator 模式可能会提供一种备选方案,如果通信必须两种方式
- 您还可以引用抽象工厂来检索需要的实例,而不需要立即将其连接起来。 >
Pretty new to dependency injection and I'm trying to figure out if this is an anti pattern.
Let's say I have 3 assemblies:
Foo.Shared - this has all the interfaces
Foo.Users - references Foo.Shared
Foo.Payment - references Foo.Shared
Foo.Users needs an object that is built within Foo.Payment, and Foo.Payment also needs stuff from Foo.Users. This creates some sort of circular dependency.
I have defined an interface in Foo.Shared that proxies the Dependency Injection framework I'm using (in this case NInject).
public interface IDependencyResolver
{
T Get<T>();
}
In the container application, I have an implementation of this interface:
public class DependencyResolver:IDependencyResolver
{
private readonly IKernel _kernel;
public DependencyResolver(IKernel kernel)
{
_kernel = kernel;
}
public T Get<T>()
{
return _kernel.Get<T>();
}
}
The configuration looks like this:
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
...
}
}
This allows me to instantiate a new object of Foo.Payment.SomeType
from inside Foo.Users without needing a direct reference:
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
}
}
This makes it unclear what the exact dependencies of the UserAccounts
class are in this instance, which makes me think it's not a good practice.
How else can I accomplish this?
Any thoughts?
Althought somewhat controversial: yes, this is an anti-pattern. It's known as a Service Locator and while some consider it a proper design pattern, I consider it an anti-pattern.
This issue is that usage of e.g. your UserAccounts class becomes implicit instead of explicit. While the constructor states that it needs an IDependencyResolver, it doesn't state what should go in it. If you pass it an IDependencyResolver that can't resolve ISomeType, it's going to throw.
What's worse is that at later iterations, you may be tempted to resolve some other type from within UserAccounts. It's going to compile just fine, but is likely to throw at run-time if/when the type can't be resolved.
Don't go that route.
From the information given, it's impossible to tell you exactly how you should solve your particular problem with circular dependencies, but I'd suggest that you rethink your design. In many cases, circular references are a symptom of Leaky Abstractions, so perhaps if you remodel your API a bit, it will go away - it's often surprising how small changes are required.
In general, the solution to any problem is adding another layer of indirection. If you truly need to have objects from both libraries collaborating tightly, you can typically introduce an intermediate broker.
- In many cases, a Publish/subscribe model works well.
- The Mediator pattern may provide an alternative if communication must go both ways.
- You can also introduce an Abstract Factory to retrieve the instance you need as you need it, instead of requiring it to be wired up immediately.
这篇关于使用依赖注入注射依赖注射器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!