我应该在Execute中调用CanExecute吗?

本文关键字:CanExecute 调用 Execute 我应该 | 更新日期: 2023-09-27 18:04:44

给定System.Windows.Input。作为两个主要方法:

interface ICommand {
  void Execute(object parameters);
  bool CanExecute(object parameters);
  ...
}

我希望CanExecute(…)在command支持的框架中被调用之前,Execute(…)

然而,在我的命令实现内部,是否有任何理由在我的Execute(…)实现中添加CanExecute(…)调用?

例如:

public void Execute(object parameters){
  if(!CanExecute(parameters)) throw new ApplicationException("...");
  /** Execute implementation **/
}

这在我的测试中变得相关,因为我可能模拟出一些接口来支持CanExecute,并且在测试Execute时必须进行相同的模拟。

有什么设计想法吗?

我应该在Execute中调用CanExecute吗?

程序员是出了名的懒惰,他们调用Execute而不先调用CanExecute

ICommand接口更常与WPF绑定框架一起使用,但是它是一个非常健壮和有用的模式,可以在其他地方使用。

我立即从Execute方法调用CanExecute来验证对象的状态。它有助于减少重复的逻辑,并强制使用CanExecute方法(为什么要费那么大的力气去确定是否可以调用一个方法而不强制使用它呢?)我不认为多次调用CanExecute有什么问题,因为它应该是一个快速的操作。

但是,如果CanExecute方法返回false,我总是记录调用Execute方法的结果,以便消费者知道结果。

对于在Execute实现中添加对CanExecute的调用,我不会像其他人那样乐观。如果您的CanExecute执行需要很长时间才能完成,该怎么办?这意味着在现实生活中,您的用户将等待两倍的时间-一次是CanExecute被环境调用时,然后是被您调用时。

您可以添加一些标志来检查CanExecute是否已经被调用,但是要注意使它们始终保持在命令状态,以免在状态发生变化时错过或执行不必要的CanExecute调用。

我会选择其中之一,但不会两者兼得。

如果你期望用户调用CanExecute,那么不要在Execute中调用它。你已经在你的界面中设置了这种期望,现在你和所有的开发人员都有了一个合约,暗示了这种与iccommand的交互。

然而,如果你担心开发人员没有正确地利用它(你可以正确地),那么我建议将它从接口中完全删除,并使其成为实现问题。

的例子:

interface Command {
    void Execute(object parameters);    
}
class CommandImpl: ICommand {
    public void Execute(object parameters){
        if(!CanExecute(parameters)) throw new ApplicationException("...");
        /** Execute implementation **/
    }
    private bool CanExecute(object parameters){
        //do your check here
    }
}

这样,你的契约(接口)是清晰和简洁的,你不会对CanExecute是否被调用两次感到困惑。

然而,如果你真的无法控制这个接口,另一个解决方案可能是存储结果并像这样检查它:

interface Command {
    void Execute(object parameters);    
    bool CanExecute(object parameters);
}
class CommandImpl: ICommand {
    private IDictionary<object, bool> ParametersChecked {get; set;}
    public void Execute(object parameters){
        if(!CanExecute(parameters)) throw new ApplicationException("...");
        /** Execute implementation **/
    }
    public bool CanExecute(object parameters){
        if (ParametersChecked.ContainsKey(parameters))
            return ParametersChecked[parameters];
        var result = ... // your check here
        //method to check the store and add or replace if necessary
        AddResultsToParametersChecked(parameters, result); 
    }
}

我不认为添加它有问题。正如你所说,通常框架会在Execute之前调用CanExecute(例如使按钮不可见),但是开发人员可能出于某种原因决定调用Execute方法——如果他们在不应该这样做的时候添加检查将提供一个有意义的异常。

Execute()中调用CanExecute()可能不会造成伤害。如果CanExecute()将返回false,通常会阻止Execute(),因为它们通常绑定到UI并且不在您自己的代码中调用。然而,没有什么强制某人在调用Execute()之前手动检查CanExecute(),因此嵌入该检查并不是一个坏主意。

一些MVVM框架确实在调用Execute()之前检查它。但是,它们不会抛出异常。它们只是不调用Execute(),所以您可能不想自己抛出异常。

如果CanExecute()返回false,您可以考虑根据Execute()会做什么来抛出异常。如果它会做的事情,将被期望完成的任何调用Execute(),那么抛出是有意义的。如果调用Execute()的效果不是那么重要,那么静默返回可能更合适。

我知道这是一个老问题,但出于参考目的,您可以在通用ICommand实现中实现SafeExecute方法,而不必在每次需要手动执行时重复调用CanExecute,像这样:

public void SafeExecute(object parameter) {
    if (CanExecute(parameter)) {
        Execute(parameter);
    }
}

当然,这不会阻止直接调用Execute(),但至少你遵循了DRY概念,避免每次执行都调用两次。

同样可以实现在CanExecute()返回false时抛出异常。

CanExecute是微软将命令与UI控件(如按钮)集成的简单方法(我会说hack)。如果我们将命令与按钮关联,FWK可以调用CanExecute并启用/禁用按钮。在这个意义上,我们不应该从我们的Execute方法中调用canexecute。

但是如果我们从面向对象/可重用的角度考虑,我们可以看到iccommand也可以用于非ui目的,例如调用my Command的编排/自动化代码。在这种情况下,自动化没有必要在调用Execute()之前调用CanExecute。因此,如果我们想让我们的iccommand实现一致地工作,我们需要调用CanExecute()。是的,确实存在性能问题,我们必须进行调整。

在我看来,. net框架在这个命令中违反了ISP,就像它如何违反ASP一样。网络会员提供商。如果我写错了请指正