我正在尝试使用的应用程序使用 MVVM。这篇文章的最大部分是对我尝试过的内容以及我的工作内容的解释。问题在帖子底部附近。二手 Localizer
class 仅在此处用作示例,可以轻松替换为另一个 class。
我有一个 class library
与 Localizer
类(class)。此类的目的是动态更改应用程序的语言,而无需重新启动应用程序。 `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
在应用程序级别上,以便在其应用程序中的任何地方使用它。经过一些研究,很多人表示这是一种不好的做法,并建议调查 依赖注入(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 解决了一些问题,其中几个主要问题是:
它看起来如何?
你甚至不应该看到容器! - 你应该只看到组件的依赖关系,其余的应该看起来很神奇......
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
给你(当然除非你需要它)。需要注意的几点
其他
这绝不是 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/