7

是否有可能和/或一个好主意,用Ninject(或任何其他IoC容器,对于这个问题)来创建一个默认绑定适用于不存在适当实现的情况,并使用此默认绑定而不是在多个绑定存在时处理ActivationException,或者对于特定请求不存在绑定?使用Ninject和有约束力的默认实现,同时避免可怕的服务定位器反模式

我一直在使用Ninject的FactoryConventions扩建项目去过,但我想知道,如果他们掩盖一个错误,我在更根本的层面进行,所以我创建了一个测试来说明我想要什么这样做,因为简单,因为我可以:

考虑以下几点:

public interface IWidget { } 
public class DefaultWidget : IWidget { } 
public class BlueWidget : IWidget { } 

而且使用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`) 
} 

我不确定这条路究竟如何走下去,如果甚至可能的话,会导致我对服务定位器模式的执行不当,除非它似乎是那些回答类似问题的人给出的警告。

所以,这是IoC容器的滥用/误用做我要求做的事情吗?

看来我应该想要将类型与IoC绑定/解析为所有显式类型,因此我认为这部分看起来并不错。在现实生活中,会有很多更明确的绑定像BlueWidget,以及对“RedWidget”字符串值在未来未知数量的变化。

一般来说,这似乎是一个默认实现的概念的一些接口是一种并不罕见的情况,那么如果不在IoC容器的领域内,这种解析请求的机制在哪里?

我还计划使用工厂模式创建IWidget实现。到目前为止,我一直使用Ninject.Extensions.Factory自动创建的工厂与自定义实例提供程序和自定义绑定生成器,但我无法解决这个问题。

对工厂实现有更多的控制(换句话说,使用我自己的工厂,而不是Ninject.Extensions.Factory的自动工厂)有帮助吗?过去,我使用Assembly反射来查找候选类型,并使用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); 
     } 
    } 
} 

我能得到这个测试通过:

[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>(); 
} 

它的工作原理,但感觉不对,有以下原因:

  1. 我必须将IKernel注入IWidgetFactory
  2. 对于每个新执行的IWidgetIWidgetFactory必须更新
  3. 这似乎是一个Ninject扩展名应该为这种情况已经

UPDATE OF END 1

会是怎样被创造你在这种情况下做的是,考虑到IWidget实现的计数很高,“widget name”参数的预期范围本质上是无限的,并且所有不可解析的widget名称应该被处理为w ith 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>(); 
} 
+0

你问一个很好的问题,并已明确提出在大量的工作和我的很高兴+1。我个人很困惑#8的结果并手动验证!您的下一步是查看命名绑定的测试,并且a)尝试理解它们以改进您的问题b)尝试理解它,以便您可以使用自己的绑定元数据实现您的解决方案(相信我这很容易)但理想情况下,您会从测试中发现您观察的所有内容是否有意义,然后提交错误报告或合并请求或对您的问题进行更改 –

+0

@Ruben,感谢您的评论 - 这些测试是在主要Ninject GitHub存储库?我找不到像“NamedBindingsTests”这样的测试文件,尽管我在“Planning”文件夹中找到了BindingMetadata。 – Jeff

+0

邓诺 - 只是猜测基于我以前的探索,2s ...似乎https://github.com/ninject/ninject/blob/master/src/Ninject.Test/Integration/StandardKernelTests.cs是最接近的。无法回想起是否存在维基,博客文章或被广告投放的关于默认命名行为的答案,因为您问的是,但我非常肯定它在某处(我个人做的都是全名或全名,所以它从来没有作为关注) –

回答

2

不知道这个堆叠在哪里,但它的工作原理。虽然你不能传递一个命名参数,它只能用一个空参数。换句话说,你不能这样做:

var stuff1 = kernel.Get<IWidget>("OrangeWidget"); 

除非OrangeWidget存在。相反,以获取默认你需要做的是:

var stuff2 = kernel.Get<IWidget>(); 

下面是一个例子:

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>(); 

This是一个很酷的Ninject后你可能感兴趣的......

我d喜欢添加一些关于命名参数的内容。这是基于this文档。 Named参数允许您在获取它们时按名称调用绑定,但它不会默认任何事物。所以你实际上必须传递名称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"); 

如果任何人都可以找出如何实现一个默认的,我很好奇,想知道怎么回事,但我从来没有需要它。我是Ninject的学生,非常喜欢它。

UPDATE:

我知道了。找到了一个很酷的解决方案here

我创建了一个类来扩展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>(); 
    } 
} 

这是你的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>(); 
} 
+0

有趣 - 你的第一个例子与我的“evolution#5”类似(参见我的问题底部的测试演变列表),并且我能够在不使用额外绑定条件的情况下使用'When。(x = > ... ...“,因为我发布了这个问题,我开始相信我试图在绑定中插入特定类型的默认回退逻辑来滥用Ninject的功能,并且我使用了简单的switch语句在我的代码中检索一个'DefaultWidget',当它找不到我请求的特定的。 – Jeff

+0

它可能是更多的一般设计问题,也许有[程序员]的答案(http:// programmers因为这个问题的一部分处理Ninject的功能,另一部分让你想知道该逻辑是否应该耦合到任何IoC容器 - Ninject或其他。 – Jeff

+0

我实际上使用IOC C并将所有“新”语句放在绑定模块中。然后我打电话给所有这样的IOCCOntainer.instance.Get ()。我发现它确实组织了我的代码,我可以通过指向不同的绑定模块来切换到测试模式来实例化我的模拟。我嘲笑我的所有服务,而且有些人觉得第三方的模拟效果更好。这样我的第一次测试很容易。 Ninject很容易做到这一点,所以这就是为什么我喜欢它,以及为什么我会像Stackoverflow一样围绕网站学习更多技巧。你的问题很有趣,因为我从不使用默认值。 – CodeChops