使用Ninject并绑定默认实现,同时避免可怕的Service Locator反模式

时间:2013-04-16 21:44:00

标签: dependency-injection ninject ioc-container anti-patterns service-locator

是否有可能和/或一个好主意使用Ninject(或任何其他IoC容器)为不存在适当实现的情况创建默认绑定,并且当存在多个绑定或者特定请求没有绑定时,使用此默认绑定而不必处理ActivationException

我一直在使用Ninject的FactoryConventions扩展项目,但我想知道他们是否正在掩盖我在更基础的层面上犯的错误,所以我创造了一个测试来说明我想要做的事情,就像我能做的那样:

鉴于以下内容:

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

使用xUnit进行以下FluentAssertions测试:

[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. 对于IWidget的每个新实施,IWidgetFactory必须更新
  3. 似乎已经为此方案创建了Ninject扩展
  4. END OF UPDATE 1

    在这种情况下你会做什么,考虑到IWidget实施的计数很高,“widget name”参数的预期范围基本上是无限的,并且所有无法解析的小部件名称应该使用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>();
    }
    

1 个答案:

答案 0 :(得分: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帖子......

我想添加一些有关命名参数的内容。这基于this文档。 Named参数允许您在获取&lt;&gt;时按名称调出绑定他们,但它没有“默认”任何东西。因此,您实际上必须传递名称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的学生,非常喜欢它。

<强>更新

我明白了。找到了一个很酷的解决方案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>();
}