如何从大视图模型中分离命令

All*_*ech 1 c# architecture wpf mvvm

我的viewmodel包含很多命令,它使我的viewmodel非常大.我想从viewmodel中分离出我的命令.目前,我的解决方案是为每个命令创建一个类,如下所示,

 public class TestCommand : CommandBase
{
    private MainViewModel vm;

    public TestCommand(MainViewModel vm)
    {
        this.vm = vm;
    }

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

    public override void ExecuteCommand(object parameter)
    {
        vm.logger.log(...);
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

由于我需要在ViewModel中使用某些方法或属性,因此我必须将viewmodel作为参数传递给命令.对于这个解决方案,有两个缺点:1.项目中有很多命令文件,如果一个视图中命令的平均计数为15,则10个视图将在项目中有150个命令文件; 2.将ViewModel作为参数传递给命令需要一些属于私有的属性或方法必须更改为public; 将viewmodel传递给命令也很奇怪.

有没有其他解决方案来分离命令?

Tse*_*eng 7

TL; DR:

ViewModel是表示逻辑,主要用命令表示,因此命令占用ViewModel的大部分代码并不罕见.不要太努力使ViewModel成为普通数据持有者(在使用ViewModels时常见于ASP.NET MVC中)INotifyPropertyChanged.

长版

由于缺乏更多的细节,很难给你具体的提示,但这里有一些一般的指导方针.您可以使用有关您正在使用的命令类型的更多详细信息更新您的问题,我将尝试更新问题.

  1. 演示逻辑

    ViewModels主要关注的是演示.ViewModels中没有商业逻辑的地方.

    必须将业务逻辑提取到您的业务/域模型(如果您遵循富域模型)或服务(在贫血域模型中).在富域模型中,您的服务层通常非常薄,主要用于协调多个模型之间的操作.

    因此,如果您的ViewModel /命令正在执行与演示无关的任何类型的逻辑(如果单击按钮A,禁用按钮B,C和D或隐藏GroupBoxA或"如果数据丢失CanExecute,则禁用按钮A" ICommand)它可能做得太多.

  2. 关注点分离:

    您的ViewModel可能会尝试执行比预期更多的操作.示例中的记录器就是这样一个提示.记录不是ViewModel关注的问题.

    ViewModel是关于表示和表示逻辑的,而日志记录是一个应用程序问题(因为它不属于域/业务逻辑).

    通常,ViewModel可以拆分为两个或更多ViewModel(即管理客户列表并允许编辑所选客户的ViewModel,通常可以拆分为2或3个ViewModel :( CustomersViewModel显示列表),CustomerDetailViewModel或者CustomerViewModel(客户详细信息))和a CustomerEditViewModel(编辑有问题的客户)

    记录和缓存等问题应该使用ie Decorator模式完成.这要求您的服务和/或存储库正确使用和实现接口,然后您可以创建用于缓存或日志记录的装饰器,而不是注入服务的原始实例,您实现装饰器.

    依赖注入(DI)和控制反转(IoC)容器确实可以帮助您实现这一目标.不得不手动连接它(也就是穷人的DI)是对接的痛苦.具体的例子超出了这个答案的范围.

  3. 命令中的业务逻辑

    命令不应包含业务逻辑.当您的命令包含太多代码(通常超过5-20行代码)时,这是一个很好的线索,您的命令可能会做太多.

    命令实际上应该只连接多个服务调用并将数据分配给Properties和/或引发事件/消息(与表示层相关.不要与域事件混淆,域事件不应该在命令中引发).它们类似于MVC中的"Actions"(例如ASP.NET MVC中使用的框架).

    命令通常应该是这样的

    var customer = new Customer { Name = this.CustomerName, Mail = this.CustomerMail };
    try {
        this.customerService.AddCustomer(customer);
        // Add it to Observable<Customer> list so the UI gets updated
        this.Customers.Add(customer);
        // the service should have populated the Id field of Customer when persisting it
        // so we notify all other ViewModels that a new customer has been added
        this.messageBus.Publish(new CustomerCreated() { CustomerId = customer.Id } );
    } catch (SomeSpecificException e) { // Handle the Exception } 
    
    Run Code Online (Sandbox Code Playgroud)

    要么

    this.Customers = this.customerRepository.GetAll();
    // Or this for async commands
    this.Customers = await this.customerRepository.GetAllAsync();
    
    Run Code Online (Sandbox Code Playgroud)
  4. 封装

    许多命令与ViewModel本身紧密耦合,需要访问ViewModel或Model的内部状态(Model不应直接暴露给View,这会将Model耦合到View,并且模型中的任何更改都会中断你的视图和绑定).

    ICommands如果不打破封装,将这些移出ViewModel可能会很困难.

当然,您也可以在一个类中实现多个命令

public class MyViewModelCommandHandler
{
    private readonly IMyRepository myRepository;

    public MyViewModelCommandHandler(/* pass dependencies here*/)
    {
        // assign and guard dependencies

        MyCommand = new RelayCommand(MyCommand, CanExecuteMyCommand);
        MyOtherCommand = new RelayCommand(MyOtherCommand, CanExecuteMyOtherCommand);
    }

    public ICommand MyCommand { get; protected set; } 
    public ICommand MyOtherCommand { get; protected set; } 

    private void MyCommand() 
    {
        // do something
    }

    private void CanExecuteMyCommand() 
    {
        // validate
    }

    private void MyOtherCommand() 
    {
        // do something else
    }

    private void CanExecuteMyOtherCommand() 
    {
        // validate
    }
}
Run Code Online (Sandbox Code Playgroud)

在ViewModel中只需分配这些命令即可

public class MyViewModel : ViewModelBase 
{
    public MyViewModel()
    {
        var commandHandler = new MyCommandHandler(this);
        OneCommand = commandHandler.MyCommand;
        OtherCommand = commandHandler.MyOtherCommand;
    }

    public ICommand OneCommand { get; private set; } 
    public ICommand OtherCommand { get; private set; } 
}
Run Code Online (Sandbox Code Playgroud)

您还可以MyCommandHandler使用IoC容器将您注入到视图中,这需要稍微重新构建命令处理程序类,以创建ICommand按需.然后你可以像使用它一样

public class MyViewModel : ViewModelBase 
{
    public MyViewModel(MyCommandHandler commandHandler)
    {
        OneCommand = commandHandler.CreateMyCommand(this);
        OtherCommand = commandHandler.CreateMyOtherCommand(this);
    }

    public ICommand OneCommand { get; private set; } 
    public ICommand OtherCommand { get; private set; } 
}
Run Code Online (Sandbox Code Playgroud)

但这只会改变你的问题,但它不会解决点1到5.所以我建议先尝试上面列表中的建议,如果你的命令仍然包含"太多代码行",请尝试其他解决方案.

我不太喜欢它,因为它创造了不必要的抽象,收益微乎其微.

ViewModel主要由表示逻辑组成,这并不罕见,因为它们的目的和表示逻辑通常在命令内部.除此之外,你只有属性和构造函数.除了检查值是否更改,然后分配和一个或多个OnPropertyChanged调用之外,属性不应该有任何其他内容.

因此,50-80%的ViewModel是来自命令的代码.