使用dependency injection注入dependency injection器
对dependency injection很新,我试图弄清楚这是否是反模式。
假设我有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中定义了一个接口,它代理我正在使用的dependency injection框架(在本例中为NInject)。
public interface IDependencyResolver { T Get(); }
在容器应用程序中,我有一个这个接口的实现:
public class DependencyResolver:IDependencyResolver { private readonly IKernel _kernel; public DependencyResolver(IKernel kernel) { _kernel = kernel; } public T Get() { return _kernel.Get(); } }
配置如下所示:
public class MyModule:StandardModule { public override void Load() { Bind().To().WithArgument("kernel", Kernel); Bind().To(); // <- binding to different assembly ... } }
这允许我从Foo.Users内部实例化Foo.Payment.SomeType
的新对象,而无需直接引用:
public class UserAccounts:IUserAccounts { private ISomeType _someType; public UserAccounts(IDependencyResolver dependencyResolver) { _someType = dependencyResolver.Get(); // <- this essentially creates a new instance of Foo.Payment.SomeType } }
这使得不清楚UserAccounts
类在这个实例中的确切依赖性是什么,这让我觉得这不是一个好习惯。
我怎么能做到这一点?
有什么想法吗?
虽然有点争议:是的,这是一种反模式。 它被称为服务定位器 ,虽然有些人认为它是一种合适的设计模式,但我认为它是一种反模式。
这个问题是,例如您的UserAccounts类的使用变为隐式而非显式 。 虽然构造函数声明它需要一个IDependencyResolver,但它没有说明它应该包含什么。 如果你传递一个无法解析ISomeType的IDependencyResolver,它就会抛出。
更糟糕的是,在以后的迭代中,您可能想要在UserAccounts中解析其他类型 。 它将编译得很好,但是如果/当类型无法解析时,可能会在运行时抛出。
不要走那条路。
根据给出的信息,我们无法确切地告诉您如何使用循环依赖来解决您的特定问题,但我建议您重新考虑您的设计。 在许多情况下,循环引用是Leaky Abstractions的一个症状,所以如果你稍微改造一下你的API,它就会消失 – 通常会令人惊讶的是需要多少小的变化。
通常,任何问题的解决方案是添加另一层间接。 如果您确实需要两个库中的对象紧密协作,您通常可以引入中间代理。
- 在许多情况下, 发布/订阅模型运行良好。
- 如果通信必须双向进行,则Mediator模式可以提供替代方案。
- 您还可以引入抽象工厂来根据需要检索所需的实例,而不是要求它立即连接。
我同意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,但它确实支持属性注入 – 这是文档 。
通常IoC容器使用构造函数注入 – 它们将依赖项传递给依赖于它们的类的构造函数。 但是当存在循环依赖时,这不起作用。 如果类A和B彼此依赖,则需要将类A的实例传递给类B的构造函数。但是为了创建A,需要将类B的实例传递给它的构造函数。 这是一个鸡与蛋的问题。
使用属性注入,您可以告诉IoC容器首先调用构造函数,然后在构造的对象上设置属性。 通常,这用于可选的依赖项,例如记录器。 但是你也可以用它来打破两个需要彼此的类之间的循环依赖。
这不是很好,我肯定建议重构你的类来消除循环依赖。
这对我来说似乎有点奇怪。 是否有可能将需要两个引用的逻辑分离到第三个程序集中以打破依赖关系并避免风险?