Ninject:注入两个相同类型的不同对象
如果我有一个类,它对同一类型有双重依赖(需要两个不同的实例),如果实例之间的唯一区别是更深的依赖,那么让 Ninject 执行 DI 并保持两个图分离的最佳方法是什么?
示例对象图:
foo → ClassA → ClassB
bar → ClassA → ClassB
类 C
的构造函数:
public class C
{
public C(ClassB foo, ClassB bar) { … }
}
那么我如何确保 ClassB
实例化为 < code>foo 作为 ClassB
依赖项 foo
提供,而 bar
...bar
?
只是为了一些背景,我有一些需求更改,所以我需要用复合只写存储库替换只写存储库(IAdd)
public class CompositeWriteRepository<T> : IAdd<T>
{
public CompositeWriteRepository(IAdd<T> foo, IAdd<T> bar, Func<T, bool> descriminator) { ... }
public Add(T entity)
{
if (descriminator(entity)) {
foo.Add(entity);
} else {
bar.Add(entity);
}
}
}
通过模拟,这很容易,我可以只使用名称注入:
kernel.Bind<IAdd<EntityType>>().To<fooRepository>().Named("foo");
kernel.Bind<IAdd<EntityType>>().To<barRepository>().Named("bar");
kernel.Bind<IAdd<EntityType>>().To<CompositeWriterRepository<EntityType>>()
.WithConstructorArgument("foo", x => x.Kernel.Get<IAdd<EntityType>>("foo")
.WithConstructorArgument("bar", x => x.Kernel.Get<IAdd<EntityType>>("bar");
问题出现时我使用真实的存储库; foo
和 bar
最终写入文件,因此它们需要不同的文件名。由于它们是 StreamWriter 存储库,因此它们的依赖项之一实际上获取两个不同的文件名。
string FileName → FileStreamWriterFactory → StreamRepository → CompositeRepository
到目前为止,我发现构建事物的唯一方法是创建一个名为 FileName
、名为 FileStreamWriterFactory
、名为 StreamRepository
× 2(一次用于foo
和一次 bar
)。这看起来需要很多工作,所以我希望有更好的解决方案。
如果需要,我可以重新架构,这感觉像是一种在需求发生变化时快速添加分隔条目的优雅方式。我意识到我的类都非常具体,但我认为单一职责通常会支持这一点,就我而言,我们编写了一堆小型应用程序,并且有更多我们可以满足的请求,因此这是拥有大量可重用代码的好方法围绕这一点,我基本上只需要为不同的任务重新配置。
解决方案
雷莫·格洛尔应该得到赞扬;这可能是最好的做法。
我实际上所做的是创建一个新扩展,
public static bool WhenAnchester(this IRequest request, Func<IRequest, bool> conditions)
{
var parentContext = request.ParentContext;
if (parentContext == null) {
return false;
}
return conditions(parentContext.Request) ||
parentContext.Request.WhenAnchester(conditions);
}
这让我可以轻松控制将哪个文件注入到哪个存储库中。
kernel.Bind<string>().ToConstant("Foo.txt")
.When(x => x.Target.Name == "filepath" &&
x.WhenAnchester(t => t.Target != null && t.Target.Name == "Dest1"));
kernel.Bind<string>().ToConstant("Bar.txt")
.When(x => x.Target.Name == "filepath" &&
x.WhenAnchester(t => t.Target != null && t.Target.Name == "Dest2"));
可能有更好的解决方案,所以我没有必要向其他人推荐这个,但它对我来说效果很好。
If I have a class, which has dual dependencies on the same type (needs two different instances), if the only difference between the instances is a deeper dependency, what's the best way to have Ninject perform DI and keep the two graphs separate?
Example object graph:
foo → ClassA → ClassB
bar → ClassA → ClassB
Class C
's constructor:
public class C
{
public C(ClassB foo, ClassB bar) { … }
}
So how do I make sure that the ClassB
instantiated with foo
gets supplied as the ClassB
dependancy foo
, and bar
… bar
?
Just for some background, I had some requirements change, so I need to replace a write-only repository (IAdd) with a composite write-only repository
public class CompositeWriteRepository<T> : IAdd<T>
{
public CompositeWriteRepository(IAdd<T> foo, IAdd<T> bar, Func<T, bool> descriminator) { ... }
public Add(T entity)
{
if (descriminator(entity)) {
foo.Add(entity);
} else {
bar.Add(entity);
}
}
}
With mocking, that was easy enough, I could just inject using names:
kernel.Bind<IAdd<EntityType>>().To<fooRepository>().Named("foo");
kernel.Bind<IAdd<EntityType>>().To<barRepository>().Named("bar");
kernel.Bind<IAdd<EntityType>>().To<CompositeWriterRepository<EntityType>>()
.WithConstructorArgument("foo", x => x.Kernel.Get<IAdd<EntityType>>("foo")
.WithConstructorArgument("bar", x => x.Kernel.Get<IAdd<EntityType>>("bar");
The problem comes when I use the real repositories; foo
and bar
ultimately write to files so they need different file names. Since they're StreamWriter
repositories, one of their dependencies actually gets the two different file names.
string FileName → FileStreamWriterFactory → StreamRepository → CompositeRepository
The only way I've found thus far to construct things is to make a named FileName
, named FileStreamWriterFactory
, named StreamRepository
× 2 (once for foo
and once for bar
). This seems like a lot of work, so I hope there's a better solution.
I can re-architect if needed, it felt like an elegant way to quickly add in seperating the entries when the requirements changed. I realize my classes all are quite specific, but I think single responsibility would support this generally and in my case we write a bunch of small applications and have more requests the we can fulfill so its a good way to have lot's of re-usable code laying around that I basically just need to re-configure for different tasks.
Solution
Remo Gloor should get credit; his is probably the best practice.
What I actually did was create a new extension
public static bool WhenAnchester(this IRequest request, Func<IRequest, bool> conditions)
{
var parentContext = request.ParentContext;
if (parentContext == null) {
return false;
}
return conditions(parentContext.Request) ||
parentContext.Request.WhenAnchester(conditions);
}
This then let me easily control which file get injected into which repository.
kernel.Bind<string>().ToConstant("Foo.txt")
.When(x => x.Target.Name == "filepath" &&
x.WhenAnchester(t => t.Target != null && t.Target.Name == "Dest1"));
kernel.Bind<string>().ToConstant("Bar.txt")
.When(x => x.Target.Name == "filepath" &&
x.WhenAnchester(t => t.Target != null && t.Target.Name == "Dest2"));
There's probably a better solution, so I wouldn't necessary recommend this for others, but its working well for me.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
是的,有更好的方法
请参阅https://github.com/ninject/ninject/commit/60443badf4ef840531c93e9287b154a9bba337c2
这是 3.0,但 IsAnyAnchestorNamed 也可以在 When 条件下与 2.2 一起使用。
Yes there is a better way
See https://github.com/ninject/ninject/commit/60443badf4ef840531c93e9287b154a9bba337c2
It's 3.0 but IsAnyAnchestorNamed can also be used with 2.2 from a When condition.