静态类到依赖注入

时间:2015-04-27 08:00:31

标签: c# wpf mvvm dependency-injection

我正在尝试上班的应用程序使用MVVM。这篇文章的最大部分是对我尝试过的内容和我工作的内容的解释。问题接近帖子的底部。使用的Localizer类仅在此处用作示例,可以轻松替换为其他类。

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

我的第一种方法我能想到的是使用Localizer方法public static class public static void Initialize。这样我可以像这样初始化Localizer

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

在应用程序级别上,并在我的类库或像这样的应用程序中的任何地方使用它

string example = Localizer.GetString(/* A key in the resource dictionary */);

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

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

所以第二种方法是依赖注入,这就是我被困住的地方。我设法让我的应用程序使用以下代码使用单个MainWindowViewMainWindowViewModel进行编译:

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

上述代码的作用是将localizer注入MainWindowViewModel。这样就不会向后面的MainWindowView代码添加额外的代码,并且视图模型必须绑定到该视图。

MainWindowViewModel中,构造函数就像这样(请注意,消息框在其他地方调用,但在此处移动以最小化代码)

ILocalizer _localizer;

public MainWindowViewModel( ILocalizer localizer)
{
    _localizer = localizer;

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

以上代码仍在编译并且运行正常,没有例外。如果UserControlsclass library的视图和视图模型都需要localizer实例,则会出现此问题。

我认为我有一个解决方案,当我在我的应用程序集中有一个UserControl但感觉它更复杂'然后我会使用static class。我通常只是将UserControl的视图模型绑定到其后面的代码中的视图。这样我就可以简单地将UserControl添加到我的.xaml代码中,就像这个<local:UserControl1 />一样,没有太多额外的喧嚣。这样,视图模型父视图模型不必关心子视图模型。

使用DI我会在我的父中执行类似的操作(子项与上一代码块中的相同)

查看

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

视图模型

public UserControl1ViewModel UC1ViewModel { get; set; }
ILocalizer _localizer;

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

以上仍然工作正常,到目前为止没有问题。唯一改变的是在父视图中设置了DataContext,并且在父视图模型中设置了DataContext的内容。

问题 我的UserControls中还有几个class library。这些可以由class library的用户使用,但他们不能改变它们。这些UserControls中的大多数都是固定的pages,它们显示有关某人,汽车等的信息。其意图是,例如,具有该人名称的标签是“英文名称”,“ Naam“用荷兰语等等(这些都在视图中声明并且工作正常)但是后面的代码中还有文本必须本地化,这就是我被困住的地方。

我是否应该像在应用程序程序集中使用UserControl一样处理问题?如果在单个父视图中使用这些UserControls中的20个以上,这会产生相反的效果。

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

2 个答案:

答案 0 :(得分:5)

<强>问题

DI并不像你看起来那么简单。有DI框架可以解决DI问题,它们是成熟的软件。

由于DI 的工作方式

,您无需自行设计DI容器而无需设计DI容器

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

  • IoC - 通过在组件类之外移动解析和提供依赖关系来确保组件没有紧密耦合

  • 生命周期范围 - 确保组件具有明确定义的生命周期/生命周期,并且可以在应用程序的关键点正确实例化和处理它们

外观如何?

你甚至不应该看到容器! - 你应该只看到组件依赖,其余应该看起来像魔术......

DI容器应该非常透明。您的组件和服务应该仅通过指定依赖项(在其构造函数中)

来要求它们的依赖项

我目前的问题是什么?

您不希望必须使用以下代码手动连接子依赖项:

public MainWindowViewModel(ILocalizer localizer)
{
    _localizer = localizer;
    UC1ViewModel  = new UserControl1ViewModel(localizer); // <-- ouch
}

上述问题有很多:

  1. 您让MainWindowViewModel负责创建UC1ViewModel并管理对象的生命周期(这并不总是坏事,因为有时候您想要管理生命周期特定组件中的对象)

  2. 您正在将MainWindowViewModel的实现与UserControl1ViewModel的构造函数实现相结合 - 如果您在UserControl1ViewModel中需要另一个依赖项,则突然您必须更新MainWindowViewModel注入那个依赖,提示很多重构。这是因为您自己实例化该类型而不是让容器执行它。

  3. 容器如何阻止上述代码?

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

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

    它还跟踪依赖关系生命周期(singleton,instanced等)

    好的,我已经注册了所有内容,下一步是什么?

    一旦注册了所有依赖项,就可以从容器中解析根组件。这被称为组合根,应该是进入点&#39;为您的应用程序(通常是主视图或主要方法)。

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

    示例:

    (伪代码)

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

    现在,当您的应用程序运行时,所有依赖项都会注入根目录和根目录的所有依赖项中,依此类推

    您的MainWindowViewModel可以指定对UC的依赖:

    public MainWindowViewModel(UC1ViewModel vm)
    {
    }
    

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

    需要注意的几点

    • 您应该传递容器的实例。如果您在应用程序启动期间的任何地方引用应用程序代码中的容器,那么您可能做错了什么

    • 通常使用工厂(专门为代表组件从容器中解析的类型)实现延迟解决依赖关系。应该将工厂注入组件,然后组件可以调用工厂来获取它所需的实例。这也允许您将参数传递给依赖项。

    • 使用SOLID原则,取决于抽象而非具体类。这样,如果您决定更改某些工作方式,那么更换组件会更容易(您只需更改注册代码以使用实现相同界面的其他具体类,等等,不要重构应用程序)< / p>

    其他

    这绝不是DI的简洁视图,需要考虑很多,但希望它能帮助您入门。正如Steven所说,如果您计划重新分发库,您应该阅读最佳实践。

    dos / dont&#39>上的原始帖子在这里:

    Dependency Inject (DI) "friendly" library

    您应该使用哪个DI容器?

    世界是你的牡蛎。我是温莎城堡的粉丝 - 它不是最快的(我不能想到我已经写过的应用程序,我需要将组件分辨率快速地变成忍者。 ..),但它确实功能齐全。

    更新:我没有真正解决的一些非查询

    <强>插件

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

    其他内容

    还有很多MVVM框架在视图/视图模型解析(viewmodel-first,view-first,hybrid methods)上有几种不同的方法。

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

答案 1 :(得分:-1)

看看这篇关于WPF应用程序本地化的文章:

http://www.codeproject.com/Articles/299436/WPF-Localization-for-Dummies

您的本地化可以通过您需要支持的每种语言的资源程序集来处理,并且将在运行时根据当前文化或后备文化使用正确的本地化。您的视图模型可以引用资源,不应该关心特定的区域设置。