与此其他问题相关: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 类无用......
是吗?答案 0 :(得分:4)
首先,我同意RelayCommand
和DelegateCommand
等是实现违反SOLID原则的命令的方法,因此您在此处使用单独的类替换它们的解决方案是正确的。这样做也可以使您的ViewModel更加清洁。
也就是说,通过在ViewModels层中使用一个类(CreateCategoryCommand
)了解视图层(CreateCategoryView
)中的具体内容,您违反了MVVM。 ViewModels图层中的任何内容都不应直接引用Views图层中的任何内容。
以这种方式想象一下 - 您已将图层分成不同的dll - Views.dll,ViewModels.dll,Models.dll,DataLayer.dll。如果ViewModel中的某些内容引用了Views中的具体内容,显然你的Views将引用ViewModels(如果需要),那么你就会遇到循环引用问题。
解决方案是让您的View对象实现一个接口(接口隔离原则),如IDialog
或IUiDisplay
(根据您想要的抽象方式选择名称),并让您的命令具有依赖于该接口,而不是直接的具体类型,如下所示:
在视图中:
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
。