繁体   English   中英

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

[英]ICommand - Should I call CanExecute in Execute?

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

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

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

但是,在我的Command实现的内部,是否有任何理由在我的Execute(...)实现中添加CanExecute(...)调用?

例如:

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

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

对此有何设计想法?

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

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

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

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

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

如果您希望用户调用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); 
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM