c# - 静态类到依赖注入(inject)

标签 c# wpf mvvm dependency-injection

我正在尝试使用的应用程序使用 MVVM。这篇文章的最大部分是对我尝试过的内容以及我的工作内容的解释。问题在帖子底部附近。二手 Localizer class 仅在此处用作示例,可以轻松替换为另一个 class。

我有一个 class library Localizer 类(class)。此类的目的是动态更改应用程序的语言,而无需重新启动应用程序。 `Localizer 在使用之前必须被实例化,但一旦实例化,应该可以在整个应用程序中使用。 (该类使用应用程序资源来本地化应用程序。)

我的 第一种方法 我能想到的是制作 Localizer public static classpublic static void Initialize方法。这样我就可以初始化 Localizer像这样

Localizer.Initialize(/* Needed arguments here */);

在应用程序级别并在我的类库或这样的应用程序中任何我想要的地方使用它
string example = Localizer.GetString(/* A key in the resource dictionary */);

考虑到类库是我写的(只有我有源代码)并被其他对源代码一无所知的人使用(他们只知道类库可以做什么),我必须以某种方式明确说明他们需要调用的“如何使用这个类库”Localizer.Initialize在应用程序级别上,以便在其应用程序中的任何地方使用它。

经过一些研究,很多人表示这是一种不好的做法,并建议调查 依赖注入(inject) (DI) 和 控制反转 (IoC),所以我做到了。我了解到 DI 的做法与我的第一种方法大致相同,但删除了静态内容,使用 Localizer.Initialize作为构造函数并将实例化的类注入(inject)到我的其他类中。

所以第二种方法 是依赖注入(inject),这就是我被卡住的地方。我设法让我的应用程序用一个 MainWindowView 编译和 MainWindowViewModel使用以下代码:
protected override void OnStartup(StartupEventArgs e)
{
    ILocalizer localizer = new Localizer(Current.Resources, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name, "Languages", "Language", "en");

    var mainWindowViewModel = new MainWindowViewModel(localizer);

    var mainWindowView = new MainWindowView { DataContext = mainWindowViewModel };

    mainWindowView.Show();

    base.OnStartup(e);
}

上面代码的作用是注入(inject)localizer进入 MainWindowViewModel .这样就不会向 MainWindowView 添加额外的代码代码隐藏并具有 View 模型绑定(bind)到的 View 。

MainWindowViewModel构造函数是这样的(请注意,消息框在其他地方被调用,但移到此处以最小化代码):
ILocalizer _localizer;

public MainWindowViewModel( ILocalizer localizer)
{
    _localizer = localizer;

    MessageBox.Show(_localizer.GetString(/* A key in the resource dictionary */));
}

上面的代码仍然编译运行正常,没有异常。当我有 UserControls 时会出现问题在我的 class library View 和 View 模型也需要 localizer实例。

当我有 UserControl 时,我想我有一个解决方案在我的应用程序程序集中,但感觉比使用 static class 时更“复杂” .我通常只是绑定(bind)一个 UserControl 的 View 模型到其背后代码中的 View 。这样我就可以简单地添加 UserControl到我这样的 .xaml 代码 <local:UserControl1 />没有很多额外的喧嚣。这样 View 模型父 View 模型不必关心 subview 模型。

使用 DI 我会在我的 parent 中做这样的事情( child 将与前一个代码块中的相同):

看法
<n:UserControl1 DataContext="{Binding UC1ViewModel}" />

View 模型
public UserControl1ViewModel UC1ViewModel { get; set; }
ILocalizer _localizer;

public MainWindowViewModel(ILocalizer localizer)
{
    _localizer = localizer;
    UC1ViewModel  = new UserControl1ViewModel(localizer);
}

以上仍然一切正常,到目前为止没有问题。唯一改变的是DataContext设置在父 View 和DataContext的内容中在父 View 模型中设置。

问题
我也有好几个UserControls在我的 class library .这些可以由 class library 的用户使用。但他们不能改变它们。其中大部分UserControls是固定的pages显示关于人、车等的信息。 意图是,例如带有人名的标签是英文的“Name”,荷兰语的“Naam”等(这些都在 View 和工作正常)但后面的代码中也有文本必须本地化,这就是我被卡住的地方。

我是否应该像处理 UserControl 一样处理问题?在我的应用程序程序集中?如果说其中的 20 多个,这感觉真的适得其反 UserControls用于单个父 View 。

我也觉得我没有正确地实现 DI 100%。

最佳答案

问题

DI 并不像您想象的那么简单。有一些 DI 框架可以解决 DI 问题,它们是成熟的软件。

由于 DI 的工作方式,您无法在不设计 DI 容器的情况下自己进行 DI

DI 解决了一些问题,其中几个主要问题是:

  • IoC - 通过将解决方案和依赖项的提供移到组件类之外来确保组件不会紧密耦合
  • 生命周期范围 - 确保组件具有明确定义的生命周期/生命周期,并且它们在应用程序的关键点被正确实例化和处理

  • 它看起来如何?

    你甚至不应该看到容器! - 你应该只看到组件的依赖关系,其余的应该看起来很神奇......

    DI 容器应该非常透明。您的组件和服务应该需要它们的依赖项,只需指定依赖项是什么(在它们的构造函数中)

    我目前的问题是什么?

    您不希望必须使用这样的代码手动连接子依赖项:
    public MainWindowViewModel(ILocalizer localizer)
    {
        _localizer = localizer;
        UC1ViewModel  = new UserControl1ViewModel(localizer); // <-- ouch
    }
    

    上面有很多问题:
  • 您正在制作 MainWindowViewModel负责创建UC1ViewModel并管理对象的生命周期(这并不总是一件坏事,因为有时您想管理特定组件中对象的生命周期)
  • 您正在耦合 MainWindowViewModel 的实现到 UserControl1ViewModel 的构造函数实现- 如果您在 UserControl1ViewModel 中需要另一个依赖项,突然要更新MainWindowViewModel要注入(inject)该依赖项,需要进行大量重构。这是因为您正在自己实例化类型,而不是让容器来实例化。

  • 容器如何防止上述代码?

    对于任何容器,您都应该注册组件

    容器将跟踪可能的组件和服务的列表,并使用此注册表来解决依赖关系。

    它还跟踪依赖项生命周期(单例、实例化等)

    好的,我已经注册了一切,接下来呢?

    一旦您注册了所有依赖项,您就可以从容器中解析您的根组件。这被称为组合根,应该是应用程序的“入口点”(通常是主 View 或主方法)。

    容器应该负责连接并为源自该组合根的所有内容创建依赖项。

    示例:

    (伪代码)
    public class ApplicationBootstrapper
    {
        private IContainer _container;
    
        public ApplicationBootstrapper() {
            _container = new SomeDIContainer();
    
            _container.Register<SomeComponent>().AsSingleton(); // Singleton instance, same instance for every resolve
            _container.Register<SomeOtherComponent>().AsTransient(); // New instance per resolve
            // ... more registration code for all your components
            // most containers have a convention based registration
            // system e.g. _container.Register().Classes().BasedOn<ViewModelBase> etc
    
            var appRoot = _container.Resolve<MainWindowViewModel>();
            appRoot.ShowWindow();
        }
    }
    

    现在,当您的应用程序运行时,所有依赖项都被注入(inject)到根和根的所有依赖项中,依此类推

    您的 MainWindowViewModel然后可以指定对 UC 的依赖,如下所示:
    public MainWindowViewModel(UC1ViewModel vm)
    {
    }
    

    注意 MainWindowViewModel不再需要 ILocalizer例如,它将被解析并注入(inject) UC1ViewModel给你(当然除非你需要它)。

    需要注意的几点
  • 您应该不是 传递容器的实例。如果您在应用程序代码中的任何地方引用容器而不是在应用程序启动期间,您可能做错了
  • 依赖项的延迟解析通常是通过工厂(专门设计用于代表您的组件从容器解析的类型)来实现的。工厂应该被注入(inject)到组件中,然后组件可以调用工厂来获取它需要的实例。这也允许您将参数传递给依赖项。
  • 使用 SOLID 原则,依赖于抽象而不是具体的类。这样,如果您决定更改某些工作的方式,则更换组件会容易得多(您只需更改注册代码以使用实现相同接口(interface)的不同具体类,等等,无需重构应用程序)

  • 其他

    这绝不是 DI 的简明 View ,需要考虑的内容很多,但希望它能让您入门。正如 Steven 提到的,如果您计划重新分发库,您应该阅读最佳实践。

    关于 dos/dont 的原始帖子在这里:

    Dependency Inject (DI) "friendly" library

    您应该使用哪个 DI 容器?

    世界是你的牡蛎。我是 CaSTLe Windsor 的粉丝 - 它不是最快的(我想不出我写过的应用程序需要组件分辨率来快速忍者......),但它肯定功能齐全。

    更新:几个非查询我没有真正解决

    插件

    CaSTLe Windsor 具有内置的插件功能 - 因此您可以将一个 DLL 放入您的应用程序目录,通过向容器注册组件来为您的应用程序添加功能。不确定这是否适用于您的 UC 类库(您可以让应用程序依赖它,除非它实际上需要是一个插件)

    其他

    还有相当多的 MVVM 框架在 View / View 模型分辨率上有几种不同的方法( View 模型优先、 View 优先、混合方法)。

    如果您还没有使用其中一种(听起来不像),您可能需要考虑使用其中一种来帮助指导您构建应用程序。

    关于c# - 静态类到依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29890223/

    相关文章:

    c# - MVVM检测子类的属性变化

    c# - Fiddler 和 .Net 3.5 SSL 错误。在 .Net 4.0 中工作正常

    c# - Powershell 二进制模块 : Dynamic tab completion for Cmdlet parameter values

    wpf - WPF 中非 UI 线程的捕获所有异常处理程序

    wpf - 在 MVVM 中绑定(bind) DocumentViewer

    c# - 禁用时更改按钮颜色

    c# - Unity 无法构建 apk Android

    C#内存不足异常-警告策略

    wpf - 使 ListView.ItemTemplate 中网格的宽度与 ListViewItem 的宽度相同

    c# - “System.Reflection.TargetInitationException”(MVVM Light 内部)