dependency-injection - 使用 Ninject 并绑定(bind)默认实现,同时避免可怕的 Service Locator 反模式

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

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

我一直在使用 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`)
}

我不确定沿着这条路走下去,如果可能的话,会导致我对 Service Locator 模式的一个令人讨厌的实现,除了这似乎是那些回答过类似问题的人给出的警告。

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

对于我拥有的所有显式类型,我似乎应该想用 IoC 绑定(bind)/解析类型,所以这部分对我来说似乎没有错。在现实生活中,会有更多的显式绑定(bind),例如 BlueWidget ,以及传入的“RedWidget”字符串值的未知数量的变化。

一般来说,某些接口(interface)的默认实现的概念似乎并不常见,那么如果不在 IoC 容器的范围内,这种解决请求的机制会在哪里呢?

我还打算使用工厂模式来创建 IWidget实现。到目前为止,我一直在使用由 Ninject.Extensions.Factory 自动创建的工厂以及自定义实例提供程序和自定义绑定(bind)生成器,但我无法解决这个问题。

对工厂实现有更多的控制(换句话说,使用我自己的工厂,而不是来自 Ninject.Extensions.Factory 的自动工厂)有帮助吗?过去,我使用 Assembly 反射来查找候选类型,并使用 Activation.CreateInstance() 来创建我需要的特定实现或默认实现,但是一旦这些实现具有自己的构造函数注入(inject)依赖项,这将变得非常麻烦因为依赖注入(inject)原则应用于这些实现。因此,我转向 IoC 容器寻求解决方案——但这并没有像我希望的那样奏效。

更新 1 -- 使用我自己的工厂实现成功

我对此并不满意,因为每次 IWidget 的新实现必须写,我也得打开这个工厂更新它。在我的示例中,我还必须在绑定(bind)中添加另一行 - 但这就是基于约定的绑定(bind)的用武之地,我计划使用它来避免必须不断更新绑定(bind)定义。

使用这个工厂实现,

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

它有效,但感觉不对,原因如下:
  • 我必须注入(inject) IKernel进入IWidgetFactory
  • 对于 IWidget 的每个新实现, IWidgetFactory必须更新
  • 似乎应该已经为此场景创建了一个 Ninject 扩展

  • 更新结束 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>();
    }
    

    最佳答案

    不知道这在哪里堆积,但它有效。但是,您不能传递命名参数,它仅适用于空参数。换句话说,你不能这样做:

    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 参数允许您在 Get<> 时按名称调用绑定(bind),但它不会“默认”任何内容。因此,您实际上必须传递名称 DefaultWidget 才能获得该绑定(bind)。这有效:
    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>();
    }
    

    关于dependency-injection - 使用 Ninject 并绑定(bind)默认实现,同时避免可怕的 Service Locator 反模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16047654/

    相关文章:

    c# - 在仍然使用依赖注入(inject)的同时创建新实例

    c++ - 不直接继承的覆盖方法实现注入(inject)

    Ninject 拦截具有特定属性的任何方法?

    asp.net-mvc-3 - 第一页加载缓慢 - ASP.NET MVC

    c# - StructureMap - 如何注册和解析开放泛型类型

    c# - 在 DI 注入(inject)容器中注册受约束的 MediatR 管道行为

    ninject - 使用Ninject绑定(bind)WebApi过滤器属性

    c# - 结构图配置更改基于 app.config 中的设置

    c# - 将 Nininject MVC 与类库一起使用

    c# - UnitOfWork类的结构图及生命周期