【问题标题】:Adding Dependency Injection to an MVVM application向 MVVM 应用程序添加依赖注入
【发布时间】:2015-12-09 16:51:30
【问题描述】:

尝试使用 MVVM 模式回填 WPF 应用程序以使用依赖注入。我对 DI 不太熟悉,之前只使用过一次,但我想我了解其中的原理。

我需要确保所有绑定都注册在一个地方 - 应用程序根目录。在 WPF 中,这是 OnStartup 方法。因此,我抓住了 Ninject 并将其放入我的应用程序中,以尝试将我的存储库类自动绑定到初始视图:

private void OnStartup(object sender, StartupEventArgs e)
{
    IKernel kernel = new StandardKernel();
    kernel.Bind<IRepository>().To<Repository>();

    Views.MainView view = new Views.MainView();
    view.DataContext = kernel.Get<ViewModels.MainViewModel>();

    view.Show();
}

从现在开始,我使用数据模板资源设置上下文:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:views="clr-namespace:My.Views"
                    xmlns:models="clr-namespace:My.ViewModels" >
    <DataTemplate DataType="{x:Type models:MyViewModel}" >
        <views:MyView />
    </DataTemplate>
    <!-- etc -->
</ResourceDictionary>

而且它有效。伟大的!但是,在 MainViewModel 中,我按下一个按钮并将不同类型的 ViewModel 加载到窗口中:

NavigationHelper.NewWindow(this, new QuoteViewModel(quote, new Repository()));

这行代码正是最初将我吸引到 DI 的原因——我无法对此进行测试,因为我无法在此处模拟依赖项。在这种情况下添加 DI 对我一点帮助都没有,因为我只应该在 OnStartUp 中使用我的 IoC 容器,所以我不能使用 kernel.Get 来获取我的 QuoteViewModel,对吧?

四处窥探所以我看到很多人建议我使用服务定位器解决这个问题。这对我来说是新的,而且我还看到很多人告诉我,将其用于 DI 是一种反模式,不应该与 bargepole 接触。谁是对的?

也许更重要的是,这个问题是否有一个巧妙的解决方案?我见过其他几个例子,它们需要不同软件包的大杂烩才能使其工作。现在,感觉 MVVM 和 DI 并不能很好地相互配合。

【问题讨论】:

  • 您绝对应该查看 this q/a 关于在 WPF/MVVM 中进行 DI 以及 this one 的信息。

标签: c# wpf mvvm dependency-injection inversion-of-control


【解决方案1】:

你快到了。你错过了两件事:

子视图模型的工厂

您需要一个能够为您创建子 VM 的工厂。为这个工厂引入一个接口,这样你就可以在测试中替换它。

public interface IVmFactory {
    IQuoteViewModel CreateQuoteViewModel();
}

你可以自己实现这个接口or let NInject do it for you

请务必在 DI 容器中注册该工厂,以便容器在收到实例化具有该工厂作为依赖项的类的请求时能够解析它。

ViewModels.MainViewModel 中的正确 DI

现在,您可以使用标准构造函数注入将IVmFactory 注入视图模型:

public class MainViewModel {
    public MainViewModel(IVmFactory vmFactory) {
        _vmFactory = vmFactory;
    }

    // ...
}

DI 和 MVVM

一旦您找到解决 WPF 的默认构造函数问题的方法(当您尝试让 WPF 实例化 VM 时),DI 和 MVVM 就可以很好地协同工作。

DI 显然不是反模式,but service locator is。不幸的是,通常建议将服务定位器与 MVVM 一起使用,因为它使您能够快速入门,而无需创建工厂并正确注入它们。权衡是快速开始与拥有干净且可测试的设计 - 自己决定。

【讨论】:

    【解决方案2】:

    我同意服务定位器是一种反模式,我个人通过为 StandardKernel (Injector) 创建一个封装类来解决这个问题,该类实现一个接口 (IInjector),然后通过字段注入将自身注入到需要它的类中:

    [Inject] public IInjector Injector {get; set;}
    
    void MyClassMethod()
    {
        var instance = this.Injector.Get<ISomeInterface>();
        // etc
    }
    

    这消除了服务位置反模式,并抽象出 DI 框架的特定于实现的细节。

    【讨论】:

    • 这仍然是一个伪装的服务定位器。通过将IInjector 作为依赖项,一个类基本上可以传达“我依赖于整个世界”。这会损害可读性和可测试性,并且违反接口隔离原则。
    • 是的,但这只是在所有实例都接收相同注入器的特定情况下。
    • 不,总是这样:类型方面,您可以通过注入器解析任何类型。你可以写Injector.Get&lt;string&gt;(),它会编译。这很糟糕,因为它对开发人员没有帮助,接口可以解决任何问题(类型系统明智)。你实现了Abstract Service Locator variation
    • ...你看不到一个类真正有什么依赖,你只看到它使用了注入器。
    • 好点子,我想我需要复习一下我的设计模式了。
    猜你喜欢
    • 2014-10-11
    • 1970-01-01
    • 1970-01-01
    • 2016-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-12
    相关资源
    最近更新 更多