这是使用命令模式的正确方法吗?

时间:2015-03-13 16:09:41

标签: c# wpf mvvm dependency-injection command-pattern

与此其他问题相关:How to inject an action into a command using Ninject?

根据对上述问题的评论,我认为我只需要创建一些命令类并将它们注入我的视图模型中,以便视图的控件只需要绑定到它们。我在概念上同意并理解其中的好处。此外,我希望使用Ninject,DI和Constructor Injection尽可能保持清洁。

遵循这些重要规则,这就是我到目前为止所得到的。

CreateCategoryCommand

public class CreateCategoryCommand : ICommand {
    public CreateCategoryCommand(CreateCategoryView view) {
        if(view == null) throw new ArgumentNullException("view");
        this.view = view;
    }

    public bool CanExecute(object parameter) { return true; }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter) { view.Show(); }

    private readonly CreateCategoryView view;
}

CategoriesManagementViewModel

public class CategoriesManagementViewModel {
    public CategoriesManagementViewModel(ICommand createCommand) {
        if (createCommand == null) throw new ArgumentNullException("createCommand");
        this.createCommand = createCommand;
    }

    public ICommand CreateCommand { get { return createCommand; } }

    private readonly ICommand createCommand;
}

所以现在当 CategoriesManagementView 被初始化时,它是使用 CategoriesManagementViewModel 构造注入的,而 CategoriesManagementViewModel 又是 CreateCategoryCommand ,反过来是使用 CreateCategoryView 进行构造函数注入,因此没有冗余依赖,也没有任何循环依赖。

现在,当我 CategoriesManagementView.CreateButton 时,它将触发绑定的 CategoriesManagementViewModel.CreateCommand ,它将向用户显示 CreateCategoryView ,这个视图应该有自己适当的命令,并以相同的方式注入。

最后,这会使 RelayCommand 类无用......

是吗?

1 个答案:

答案 0 :(得分:4)

首先,我同意RelayCommandDelegateCommand等是实现违反SOLID原则的命令的方法,因此您在此处使用单独的类替换它们的解决方案是正确的。这样做也可以使您的ViewModel更加清洁。

也就是说,通过在ViewModels层中使用一个类(CreateCategoryCommand)了解视图层(CreateCategoryView)中的具体内容,您违反了MVVM。 ViewModels图层中的任何内容都不应直接引用Views图层中的任何内容。

以这种方式想象一下 - 您已将图层分成不同的dll - Views.dll,ViewModels.dll,Models.dll,DataLayer.dll。如果ViewModel中的某些内容引用了Views中的具体内容,显然你的Views将引用ViewModels(如果需要),那么你就会遇到循环引用问题。

解决方案是让您的View对象实现一个接口(接口隔离原则),如IDialogIUiDisplay(根据您想要的抽象方式选择名称),并让您的命令具有依赖于该接口,而不是直接的具体类型,如下所示:

在视图中:

public class CreateCategoryView : ..., IUiDisplay
{
    ...
}

在ViewModels中:

public interface IUiDisplay
{
    void Show();
}

public class CreateCategoryCommand : ICommand 
{
    public CreateCategoryCommand(IUiDisplay uiDisplay) {
        if(display == null) throw new ArgumentNullException("uiDisplay");
        this.display = uiDisplay;
    }

    private readonly IUiDisplay display;
    ...
}

现在,您的Command不再直接依赖于更高层的具体(因此它现在可以模拟和可测试!)。现在,您可以让DI / IOC将命令依赖项解析为要注入的特定视图类。 (我个人将一个视图工厂注入到命令中,只是懒惰地创建视图,但这是一个不同的讨论。)

一个相关的注释 - 如果你通过直接让它们实现ICommand来实现命令,那么你将重复自己(DRY)。我的建议是创建一个实现CommandBase要求的抽象基类(ICommand或其他东西)。您会发现从中派生的所有命令都只会覆盖Execute(),有时会覆盖CanExecute()。这使您不必在每个命令中实现事件(以及引发事件的代码),并且在许多情况下使您不必执行CanExecute,因为大多数命令只返回true