Jef*_*eff 7 dependency-injection anti-patterns ninject ioc-container service-locator
是否可能和/或一个好主意使用Ninject(或任何其他IoC容器)为不存在适当实现的情况创建默认绑定,并使用此默认绑定而不是必须处理ActivationException当存在多个绑定,或者特定请求没有绑定时?
我一直在使用Ninject的工厂和公约扩展项目,但我想知道他们是否正在掩盖我在更基础的层面上犯的错误,所以我创建了一个测试来说明我想要做的事情,因为我尽我所能:
鉴于以下内容:
public interface IWidget { }
public class DefaultWidget : IWidget { }
public class BlueWidget : IWidget { }
Run Code Online (Sandbox Code Playgroud)
以下使用FluentAssertions进行xUnit测试:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// intention: resolve a `DefaultWidget` implementation whenever the
// 'name' parameter does not match the name of any other bound implementation
kernel.Bind<IWidget>().To<DefaultWidget>();
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
}
Run Code Online (Sandbox Code Playgroud)
我不确定究竟如何沿着这条道路走下去,如果可能的话,会让我对服务定位器模式进行令人讨厌的实施,除了似乎是回答类似问题的人提出的警告.
那么,这是滥用/滥用 IoC容器来做我要求它做的事情吗?
看起来我应该想要使用IoC为我所拥有的所有显式类型绑定/解析类型,因此这部分对我来说似乎没有错.在现实生活中,会有更多的显式绑定BlueWidget,以及"RedWidget"字符串值的未知数量的变化.
一般来说,这似乎是一个概念默认实现一些接口是一种不那么罕见的情况,所以在那里将这一机制解决的请求是,如果没有IoC容器的领域里?
我还计划使用工厂模式来创建IWidget实现.到目前为止,我已经使用Ninject.Extensions.Factory与自定义实例提供者和自定义绑定生成器自动创建的工厂了,可是我不能让过去这个问题.
将具有在工厂实现更多的控制权(换句话说,用我自己的工厂,而不是自动从工厂Ninject.Extensions.Factory)帮助?在过去,我用大会反射找到候选种类,并使用Activation.CreateInstance()来创建或者具体实施,我需要或默认实现,但是这变得非常繁琐,一旦这些实现有自己的构造函数依赖注入因为依赖注入原则适用于这些实现.因此,我转向IoC容器寻求解决方案 - 但这并不像我希望的那样有效.
更新1 - 使用我自己的工厂实施的成功
我对此并不满意,因为每次IWidget必须编写新的实现时,我都必须打开这个工厂并更新它.在我的示例中,我还必须在绑定中添加另一行 - 但这就是基于约定的绑定的位置,我计划使用它来避免不断更新绑定定义.
使用这个工厂实现,
public interface IWidgetFactory { IWidget Create(string name); }
public class WidgetFactory : IWidgetFactory
{
private readonly IKernel kernel;
public WidgetFactory(IKernel kernel) { this.kernel = kernel; }
public IWidget Create(string name)
{
switch (name)
{
case "Blue":
return this.kernel.Get<IWidget>(typeof (BlueWidget).Name);
default:
return this.kernel.Get<IWidget>(typeof (DefaultWidget).Name);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我可以通过这个测试:
[Fact]
public void WidgetBuilders_And_Customizers_And_Bindings_Oh_My()
{
StandardKernel kernel = new StandardKernel();
kernel.Bind<IWidget>().To<DefaultWidget>().Named(typeof(DefaultWidget).Name);
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
kernel.Bind<IWidgetFactory>().To<WidgetFactory>().InSingletonScope();
kernel.Get<IWidgetFactory>().Create("Blue")
.Should().BeOfType<BlueWidget>();
kernel.Get<IWidgetFactory>().Create("Red")
.Should().BeOfType<DefaultWidget>();
}
Run Code Online (Sandbox Code Playgroud)
它有效,但感觉不对,原因如下:
IKernel到IWidgetFactoryIWidget,IWidgetFactory必须更新更新结束1
在这种情况下你会做什么,考虑到IWidget实现的数量很高,"小部件名称"参数的预期范围基本上是无限的,所有不可解析的小部件名称应该用DefaultWidget?处理?
你不需要进一步阅读,但如果你感兴趣,我在尝试这个问题时尝试了各种测试:
这是我经历的测试的完整演变:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// evolution #1: simple as possible
// PASSES (as you would expect)
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #2: make the only binding to a different IWidget
// FAILS (as you would expect)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #3: add the binding to `BlueWidget` back
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #4: make `BlueWidget` binding a *named binding*
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #5: change `Get<>` request to specifiy widget name
// PASSES (yee-haw!)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #6: make `BlueWidget` binding *non-named*
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #7: ask for non-existance `RedWidget`, hope for `DefaultWidget`
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #8: make `BlueWidget` binding a *named binding* again
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #9: remove `RedWidget` specification in Get<> request
// ACTIVATION EXCEPTION (back to **more than one** binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<DefaultWidget>();
}
Run Code Online (Sandbox Code Playgroud)
不知道这在哪里,但它有效。不过,您不能传递命名参数,它仅适用于空参数。换句话说,你不能这样做:
var stuff1 = kernel.Get<IWidget>("OrangeWidget");
Run Code Online (Sandbox Code Playgroud)
除非 OrangeWidget 存在。而要获得默认值,您需要执行以下操作:
var stuff2 = kernel.Get<IWidget>();
Run Code Online (Sandbox Code Playgroud)
这是一个例子:
IDictionary dic = new Dictionary<string, string>();
dic.Add("BlueWidget", "BlueWidget");
dic.Add("RedWidget", "RedWidget");
kernel.Bind<IWidget>().To<DefaultWidget>()
.When(x => x.Service.Name != (string)dic[x.Service.Name]);
kernel.Bind<IWidget>().To<BlueWidget>().Named("BlueWidget");
var stuff1 = kernel.Get<IWidget>("BlueWidget");
var stuff2 = kernel.Get<IWidget>();
Run Code Online (Sandbox Code Playgroud)
这是一篇很酷的 Ninject 帖子,您可能会感兴趣......
我想添加一些有关命名参数的内容。这是基于此文档。Named 参数允许您在 Get<> 时按名称调用绑定,但它不会“默认”任何内容。因此,您实际上必须传递名称 DefaultWidget 才能获取该绑定。这有效:
kernel.Bind<IWidget>().To<BlueWidget>().Named("BlueWidget");
kernel.Bind<IWidget>().To<DefaultWidget>().Named("DefaultWidget");
var blueOne = kernel.Get<IWidget>("BlueWidget");
var defaultOne = kernel.Get<IWidget>("DefaultWidget");
Run Code Online (Sandbox Code Playgroud)
如果有人能弄清楚如何实现默认值,我很想知道如何实现,尽管我从来不需要它。我是 Ninject 的学生,非常喜欢它。
更新:
我得到了它。在这里找到了一个很酷的解决方案。
我创建了一个类来扩展 Ninject:
public static class NinjectExtensions
{
public static T GetDefault<T>(this IKernel kernel)
{
return kernel.Get<T>(m => m.Name == null);
}
public static T GetNamedOrDefault<T>(this IKernel kernel, string name)
{
T result = kernel.TryGet<T>(name);
if (result != null)
return result;
return kernel.GetDefault<T>();
}
}
Run Code Online (Sandbox Code Playgroud)
这是您的 Unknown_Type 方法...
public static void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
IDictionary dic = new Dictionary<string, string>();
dic.Add("BlueWidget", "BlueWidget");
dic.Add("RedWidget", "RedWidget");
kernel.Bind<IWidget>().To<DefaultWidget>().When(x => x.Service.Name != (string)dic[x.Service.Name]);
kernel.Bind<IWidget>().To<BlueWidget>().Named("BlueWidget");
// this works!
var sup = kernel.GetNamedOrDefault<IWidget>("Not here");
var stuff1 = kernel.Get<IWidget>("BlueWidget");
var stuff2 = kernel.Get<IWidget>();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3403 次 |
| 最近记录: |