我正在编写我的第一个WPF应用程序,我想请您帮助解决我遇到的一个问题。
我试图遵循MVVM模式,我来到一个点,我需要实现模态对话框。我在谷歌上搜索了一段时间,然后找到了一个解决方案。然而,在重构时,我遇到了一个难题,即使用DI(构造函数注入)来替代服务定位器。
我将引用这些:http://pastebin.com/S6xNjtWW.
我真的很喜欢Roboblob的方法:
首先:他创建了一个模态对话框(界面)的抽象。我将接口命名为IModalDialog,它看起来是这样的:
public interface IModalDialog
{
bool? DialogResult { get; set; }
object DataContext { get; set; }
void Show();
bool? ShowDialog();
void Close();
event EventHandler Closed;
}
Second:模态对话框服务的抽象:
public interface IModalDialogService
{
void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose) where TDialogViewModel : class;
void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel) where TDialogViewModel : class;
}
第三: IModalDialogService的具体实现:
public class ModalDialogService : IModalDialogService
{
public void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel, Action<TDialogViewModel> onDialogClose) where TDialogViewModel : class
{
// set datacontext
if (viewModel != null)
{
view.DataContext = viewModel;
}
((System.Windows.Window)view).Owner = System.Windows.Application.Current.MainWindow;
// register
if (onDialogClose != null)
{
view.Closed += (sender, e) => onDialogClose(viewModel);
}
view.ShowDialog();
}
public void ShowDialog<TDialogViewModel>(IModalDialog view, TDialogViewModel viewModel) where TDialogViewModel : class
{
this.ShowDialog(view, viewModel, null);
}
第四个:有更多的IModalDialog实现。每个都是实现IModalDialog的窗口派生类。
在我提出问题(描述问题)之前,我需要先解释一下:
假设我有更多的服务,比如IMessageBoxService。然后我需要在MainWindowViewModel的构造函数中声明这些依赖关系:
public MainWindowViewModel(IModalDialogService a,
IMessageBoxService b,
...)
,这样我就可以注入它们(通过手动或使用IOC容器,如Unity等)。
为了能够使用模态对话框服务,还缺少一个难题——基于某个键解析IModalDialog的具体实现的能力。
Roboblob在他的文章中使用ServiceLocator模式解决了这最后一块难题:
public class Bootstrapper
{
public static void InitializeIoc()
{
SimpleServiceLocator.SetServiceLocatorProvider(new UnityServiceLocator());
SimpleServiceLocator.Instance.Register<IModalDialogService, ModalDialogService>();
SimpleServiceLocator.Instance.Register<IMessageBoxService, MessageBoxService>();
...
SimpleServiceLocator.Instance.Register<IModalWindow, EditUserModalDialogView>(Constants.EditUserModalDialog);
}
}
所以他在MainWindowViewModel中简单地调用静态类Get并解析基于键的IModalDialog窗口的具体实现。
甚至Josh Smith在他的文章中也使用了类似的方法,但在评论中他说(DI -构造函数注入)是一个有效的选项。
引用的StackOverflow答案也描述了一个类似的可以修改和使用的WindowViewLoaderService。
所以问题是——用依赖注入取代ServiceLocator(它解决了IModalDialog的具体实现)的最好方法是什么?
我的思路是:
一种可能性是(由于项目不是很大/只由我开发)只是创建一个新的服务(例如称为IModalDialogResolver),它将创建并返回IModalDialog的具体实现的新实例。
然后我想到了IOC容器(Unity)。我没有这方面的经验。我想也许我不必写IModalDialogResolver,因为我可以用Unity容器注册IModalDialog的不同实现=>但是我如何使用MainWindowViewModel中的容器?我不能将引用传递给构造函数,因为那将是退回到ServiceLocation。
然后我想也许我可以在bootstrapper中使用一个统一容器来解析所有的服务,并在IModalDialogResolver内部使用另一个。但是我不知道对于Unity的推荐使用来说这是否是一个好主意。我知道的太少了,无法对此作出判断。但是有些东西告诉我,这不是一个好主意,因为它创建了一个隐藏的依赖于容器+,如果容器是一个单例,这将相当于只是传递引用到构造函数。
为了更好地解释我的心理障碍:我想使用IOC容器(例如Unity)来构造和注入接口。但是我不能把IModalDialog作为参数放在构造函数中。所以我可能真的需要把它包装在一个服务中,然后自己实现——但是(假设Unity可以开箱就能做到这一点),如果我不能使用它,那么把Unity放在那里就没有意义了。
我知道一种替代方法是将这个服务放入基类中,但是为了讨论的缘故,我们不考虑这个。我真的很想学习使用依赖注入来解决这个问题的正确方法。
在您的组合根目录中访问IoC容器是完全有效的。
实际上,这应该是唯一访问您的容器的位置。
在你给出的例子中,这就是所有正在发生的事情——具体的实现被注册在组合根目录下的容器中。
因此,要回答您的问题,您不需要在这里替换使用服务定位器模式,因为它只是在组合根中注册类型的一种机制,这是完全有效的。
如果你希望基于一些运行时条件实例化模态对话框服务,那么你应该注入一个模型对话框服务工厂(同样是一个在容器中注册实现的抽象),然后工厂将有一个方法来创建模型对话框服务,这个工厂方法将接受所需的运行时参数。
然后,您的工厂可以根据运行时参数适当地新建适当的模型对话框服务。或者,它也可以从容器中解析适当的模型对话框服务,这显然需要工厂具有对容器的引用。大多数容器支持自动工厂类型,因此您只需要为工厂定义接口,容器将使用约定自动实现工厂。城堡。以Windsor为例,它拥有类型化工厂设施,Unity也有一些类似的设施。