MVVM Commands – one command for everything or multiple commands?

In my last company, on handling view commands, in the ViewModelBase there were one central command implemented, like

public ICommand ToolCommand { get; set; }

And in the ViewModelBase, it’s been initialized, and all buttons hooked to this Command were differentiated by its Command Parameter:

public ViewModelBase()
{
    ToolCommand = new RelayCommand<String>(Execute, CanExecute);
}

In the view model base, the Execute and CanExecute functions were virtual (implementing such generic commands like Close, and so on), and in the derived classes, the Execute and CanExecute function was a big switch, extending base functionality, looked more or less like this:

public void Execute(String parameter)
{
    switch (parameter)
    {
        case "Refresh":
          RefreshGrid();
          break;
        case "OtherStuff":
          Other();
          break;
        default:
          base.Execute(parameter);
    }
}

In these cases, when we just added a button, we added another switch case in the proper view models.

However in my current company, the rule is that each button has specific command property, like

public ICommand RefreshCommand { get; set; }
public ICommand OtherStuffCommand { get; set; }

, and initiated one-by-one:

RefreshCommand = new RelayCommand<String>(RefreshGrid(), _canRefresh);
OtherStuffCommand = new RelayCommand<String>(OtherStuff(), _canDoOther);

In the office we can’t agree which is better in terms of compactness, beauty, debuggability. Surely we won’t rewrite working parts because of its cost, but considering new projects we find hard to discuss which has more advantages.

Which and why of these two approaches is better?

I think using the switch statement with strings is a terrible idea for the following reasons:

  1. Your commands can’t take an actual parameter because you’ve used the CommandParameter to identify the command itself.
  2. Your Execute and CanExecute methods become large blobs where it’s impossible to tell if you are actually handling all of your commands since you have to cross-reference the ViewModel with every single XAML file that is bound to it.
  3. Some commands do not need a CanExecute. If you have each command as an individual property, it’s very easy to tell which commands support CanExecute and which do not. With the switch statements, it’s a lot harder to determine that.
  4. It is very difficult to override behavior for a single command in a derived class. If you had a child ViewModel class, the only way to override behavior for one command is to copy the entire Execute(String parameter) method and change the one case statement that you’re interested in.
  5. Static analysis tools cannot tell you that your command binding in the XAML is wrong because your CommandParameter is just an arbitrary string.

I think that the downsides to the switch statement with string approach are so strong that I would encourage refactoring existing projects to not do it that way.

I think the key thing to consider here is the View im absence of the ViewModel.

Ie. In your view you will have bindings for the various buttons etc and you need to distinguish these.

Your choices will be bettween

NamedCommand(parameter)
GenericCommand(name, parameters)

I think its fairly obvious where a parameter becomes a command name rather than a parameter to the same command and in those cases I think you should prefer NamedCommand.

Although I can see there might be edge cases where the naming is difficult

The whole idea behind a Command is that it’s not specific to a view. You may want to execute the same command from a button on your view, or a menu option, or from a right click context menu, or from a myriad of other UI elements. By creating specific command classes, you gain Single Responsibility and reusability. I wouldn’t recommend having your view model’s define the behavior of their commands. You’ll just end up copy/pasting code throughout your code base.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *