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

Is this the right way to use the Command Pattern?

与另一个问题相关:How to inject an action into a command using Ninject?

根据对上述问题的评论,我认为我只需要创建一些命令 classes 并将它们注入我的视图模型,以便视图的控件只需要绑定给他们。我在概念上同意并理解其中的好处。此外,我希望使用 Ninject、DI 和构造函数注入尽可能干净。

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

创建类别命令

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,而后者又被构造函数注入CreateCategoryCommand,它又通过 CreateCategoryView 构造函数注入,因此没有冗余依赖,也没有任何循环依赖。

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

最后,这会使 RelayCommand class 变得无用...

是吗?

首先,我同意 RelayCommandDelegateCommand 等是实现违反 SOLID 原则的命令的方法,因此您在这里用单独的 class 替换它们的解决方案是正确的。这样做还可以让您的 ViewModel 更干净。

就是说,您在 ViewModels 层(CreateCategoryCommand)中有一个 class 并且了解视图层中的具体内容(CreateCategoryView). ViewModels 层中的任何内容都不应直接引用 Views 层中的任何内容。

这样想象 - 您已经将层分离到不同的 dll 中 - Views.dll、ViewModels.dll、Models.dll、DataLayer.dll。如果您的 ViewModels 中的某些内容引用了您的 Views 中的具体内容,并且显然您的 Views 将引用 ViewModels(必要时),那么您就会遇到循环引用问题。

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

浏览量:

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

在视图模型中:

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

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

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