Ninject绑定不同的实现

4
我使用Ninject有一段时间了,现在想知道如何实现与Unity app.config / web.config条目相同的功能。我确定这很简单,只是还没有找到最佳实现方法,当然Ninject文档也不是很好。我的意思是,在不更改代码的情况下,为接口提供不同的实现 - 具体应用程序知道使用哪个实现。例如,当我使用Unity时,我会有一个使用不同数据库的实时应用和单元测试库。
var repo = IoC.Get<IRepository>();

在我的实际应用程序中,我将返回一个RealRepository,而在我的单元测试中则会返回一个FakeRepository。我只需在app.config或web.config中映射这些类。
在Ninject中,由于您在代码中定义了映射,似乎没有一种方法可以决定使用哪些实现(或哪个模块),除非在代码中进行设置-但是当然,整个目的是我不想具体说明要使用哪种实现。
有没有好的方法来解决这个问题?我能想到的唯一方法是从配置文件动态选择NinjectModule实现,但那感觉并不好。
2个回答

8
似乎你正在错误地使用IoC容器作为服务定位器。这可能会导致许多问题,其中一个是测试变得更加困难。我建议采用正确的方式,使用构造函数注入而不是服务定位器。
这意味着,在代码中,你需要将依赖项通过构造函数传递进来,而不是通过服务定位器从容器中获取。请注意,这种方式提高了代码的可测试性和灵活性。
public class MyClass
{
    public void Do()
    {
        var repo = IoC.Get<IRepository>();
        ....
    }
}

你可以

public class MyClass
{
    private IRepository repo;
    public MyClass(IRepository repo)
    {
        this.repo = repo;
    }

    public void Do()
    {
        ....
    }
}

应用程序的根仅有一个 Get 方法。其他所有内容都通过构造函数传递。

这使得测试非常简单:

var testee = new MyClass(new Mock<IRepository>());

直接从消息源头来。 :) 很好的回答;我忽略了 IoC.Get<IRepository>() 暗示的误用。 - Kirk Woll
你说得对 - 当我使用Unity并将所有内容都放在配置文件中时,它让我完全避免了依赖注入,只需将其用作定位器。你让我彻底转变了思路,开始考虑如何正确地使用依赖注入。谢谢。 - Joe Enos

2
您的实时应用程序和单元测试库是否有不同的端点?其中一个可能是一个单元测试项目(nunit?),另一个是应用程序(控制台,Windows或asp.net)。每种类型的应用程序都应该独立定义其绑定,通常通过定义单独的模块(将其传递给StandardKernel的构造函数)来实现。一组提供真实应用程序映射的设置,另一个提供了您的单元测试映射。后者可能是必要的,也可能不是--理想情况下,您正在测试的特定类的依赖项应易于模拟并传入,而无需使用Ninject。实际上,我发现有很多借口方便使用Ninject,这种情况下我会创建一个单独的模块或者在测试本身中动态重新绑定内核。

在我使用的最新版本的Ninject中,惯用的方式是为不同的项目创建单独的模块。然而,在单元测试中,由于需要混合和匹配,您可能会有更多的模块。 - Mike Bailey
当您直接调用某些内容或每种实现都有测试版本时,这应该可以很好地工作。但是假设我有一个业务类不需要进行模拟,它使用Ninject来调用数据存储库。在我的测试中,我将调用业务类的生产实现,但我希望它调用我的测试数据存储库。由于业务类不知道它处于哪个环境中,因此它不会知道要调用测试repo。 - Joe Enos
我应该澄清一下我的评论。我假设业务类创建了一个新的内核,而那个内核将没有测试映射。如果这个假设是错误的,那么可能就是我的问题。 - Joe Enos
@Joe,是的,那样做是错误的——每个应用程序应该只有一个内核实例。这就是为什么有一个ASP.NET/MVC扩展,因为它描述了一种应用程序端点类型。还有另一个WCF扩展,因为技术上Web服务是另一种应用程序端点。重点是,每种类型只定义一个Kernel实例。(出于好奇,哪种类型的应用程序包含“业务类”?) - Kirk Woll
谢谢 - 把内核定义为应用程序全局的确很有意义。关于业务类,我没有描述清楚。它只是一种常规的分层方法,例如:我的前端应用程序(或单元测试)调用 myProductManager.GetProduct(id)。ProductManager 实现反过来调用 myProductRepo.GetProductFromDB(id),然后执行所需操作,并将结果返回给调用者。我想创建一个 IProductRepo 测试,但不是一个 IProductManager 测试,在 ProductManager 中定义内核时它无法正常工作。 - Joe Enos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接