ICommand - 我应该在Execute中调用CanExecute吗?

时间:2011-08-08 15:10:12

标签: c# wpf design-patterns command

System.Windows.Input.ICommand 作为2个主要方法:

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

我希望在调用 Execute(...)之前,在支持Command的框架中调用 CanExecute(...)

在我的命令实现内部,是否有任何理由在执行(...)实现中添加 CanExecute(...)调用?< / p>

e.g:

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

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

有关此的任何设计想法吗?

7 个答案:

答案 0 :(得分:7)

众所周知程序员是懒惰的,他们呼叫Execute而不先调用CanExecute

ICommand接口更常用于WPF绑定框架,但它是一种非常强大且有用的模式,可以在其他地方使用。

我立即从CanExecute方法调用Execute来验证对象的状态。它有助于减少重复逻辑并强制使用CanExecute方法(为什么要经历所有努力,无论他们是否可以调用方法而不是强制执行它?)。多次调用CanExecute我没有看到任何问题,因为无论如何它应该是一个快速操作。

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

答案 1 :(得分:3)

我会采用其中一种方式,但不是两种方式。

如果您希望用户调用CanExecute,则不要在Execute中调用它。您已经在界面中设置了这个期望,现在您与所有开发人员签订了合同,暗示这种与ICommand的交互。

但是,如果您担心开发人员没有正确使用它(正如您可能正确使用它),那么我建议将其从界面中完全删除,并将其作为实现问题。

示例:

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

答案 2 :(得分:2)

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

你可以添加一些标志来检查是否已经调用了CanExecute,但是当状态发生变化时,要小心保持它们始终处于命令状态,不要错过或执行不需要的CanExecute调用

答案 3 :(得分:1)

CanExecute()内拨打Execute()可能不会造成任何伤害。通常Execute()会在CanExecute()返回false时被阻止,因为它们通常绑定到UI而不是在您自己的代码中调用。但是,在调用CanExecute()之前,没有任何内容强迫某人手动检查Execute(),因此嵌入该检查并不是一个坏主意。

某些MVVM框架在调用Execute()之前确实会检查它。但是,他们不会抛出异常。他们根本不会调用Execute(),因此您可能不想自己抛出异常。

如果CanExecute()Execute()的内容返回false,您可以考虑是否抛出异常。如果它会在调用Execute()之后执行任何预期完成的事情,那么投掷是有意义的。如果调用Execute()的效果不那么重要,那么可能会默默地返回更合适。

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

但是如果我们从OOP /可重用的角度考虑,我们可以看到ICommand也可以用于非UI用途,例如调用我的Command的编排/自动化代码。在那种情况下,自动化在调用我的Execute()之前不必调用CanExecute。因此,如果我们想让我们的ICommand实现一致地工作,我们需要调用CanExecute()。是的,存在性能问题,我们必须随之调整。

根据我的观点,.Net框架在此命令中违反了ISP,就像它违反了ASP.Net成员资格提供者一样。如果我错了请更正

答案 6 :(得分:0)

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

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

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

同样可以实现在CanExecute()返回false的情况下抛出异常。