5

我正在编写我的第一个 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;
}

二:模态对话服务的抽象:

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 的 Window 派生类。


在我提出问题(描述问题)之前,我需要事先解释一下:

假设我有更多服务,例如 IMessageBoxService。然后我需要在MainWindowViewModel的构造函数中声明这些依赖:

public MainWindowViewModel(IModalDialogService a,                               
                           IMessageBoxService b,
                           ...)

这样我就可以注入它们(手动或使用 Unity 等 IOC 容器)。

为了能够使用模态对话服务,还缺少一个难题——基于某个键解析 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 的具体实现)的最佳方法是什么?

我的思路是:

  1. 一种可能性是(由于项目不是很大/仅由我开发)只创建一个新服务(例如称为 IModalDialogResolver),它将创建并返回 IModalDialog 的具体实现的新实例。手动注入所有服务。

  2. 然后我想到了一个 IOC 容器(Unity)。我没有这方面的经验。我想也许我不必编写 IModalDialogResolver,因为我可以使用 Unity 容器 => 注册 IModalDialog 的不同实现,但是如何在 MainWindowViewModel 中使用容器?我无法将引用传递给构造函数,因为这将是对 ServiceLocation 的退步。
    所以我想也许我可以在引导程序中使用一个统一容器来解析所有服务,并在 IModalDialogResolver 内部使用另一个容器。但我不知道这对于 Unity 的推荐用法是否是一个好主意。我真的知道太少,无法判断这一点。但是有些事情告诉我,这不是一个好主意,因为它会创建对容器的隐藏依赖 + 如果容器是单例,则相当于将引用传递给构造函数。

为了更好地解释我的心理障碍:我想使用一个 IOC 容器(例如 Unity)来构建和注入接口。但是我不能只将 IModalDialog 作为参数放在构造函数中。所以可能我真的需要将它包装在一个服务中并自己实现 - 但是(假设 Unity 可以开箱即用地做到这一点)如果我不能使用它,那么首先将 Unity 放在那里是没有意义的。

我知道其中一种选择是将这一服务放入基类中,但为了争论,我们不考虑这一点。我真的很想了解使用依赖注入解决此问题的正确方法。

4

1 回答 1

4

在您的组合根目录中访问 IoC 容器是完全有效的,并且是您所期望的。

事实上,这应该是访问容器的唯一位置。

在您给出的示例中,这就是正在发生的一切 - 具体实现正在组合根中的容器中注册。

所以要回答你的问题,你不需要在这里替换服务定位器模式的使用,因为它只是一种在组合根中注册类型的机制,这是完全有效的。

如果您希望基于某些运行时条件来实例化模态对话服务,那么您应该注入一个模型对话服务工厂(同样是在您的容器中注册的实现的抽象),然后工厂将有一个方法来创建模型对话服务和此工厂方法将采用所需的运行时参数。

然后,您的工厂可以根据运行时参数适当地新建适当的模型对话服务。或者,它也可以从容器中解析适当的模型对话服务,这显然需要工厂对容器的引用。

大多数容器都支持自动化工厂类型,因此您只需要为工厂定义接口,容器就会使用约定自动实现工厂。例如,Castle.Windsor 有Typed Factory Facility,而 Unity 有一些等价物。

于 2013-06-14T13:08:36.820 回答