【问题标题】:ICommand - Should I call CanExecute in Execute?ICommand - 我应该在 Execute 中调用 CanExecute 吗?
【发布时间】:2011-10-22 11:48:39
【问题描述】:

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

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

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

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

例如:

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

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

对此有何设计想法?

【问题讨论】:

  • @CodeNaked:这并没有让我印象深刻。这询问在Execute() 内调用CanExecute(),另一个询问在实现之外调用它。
  • @CodeNaked - 我注意到可能重复的问题缺乏强有力的答案。
  • @Joel B Fant,我也是。
  • 是的,我看到了这个问题。我在这里更关心 ICommand 接口内部的一致实现,而不关心它是如何被调用的。

标签: c# wpf design-patterns command


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    在我看来,.Net 框架在这个命令中违反了 ISP,就像它对 ASP.Net Membership 提供者的违反一样。如果我错了,请纠正

    【讨论】:

      【解决方案3】:

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

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

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

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

      【讨论】:

      • 有时有一个完全正当的理由绕过可以执行测试,比如用户无权直接调用该函数,但后台任务可以代表用户运行它。所以我会说,除非在 CanExecute 为 false 时调用 Execute 会对系统造成积极危害,然后调用 canExecute 是有限的,但是如果它是积极有害的,则没有正当理由绕过并且应该强制调用 canExecute我可能会通过缓存结果并检查而不是再次调用函数来做到这一点
      【解决方案4】:

      我会选择一种方式或另一种方式,但不会两者兼而有之。

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

      【讨论】:

      • 抱歉,我的问题并不清楚这是我正在使用的 WPF ICommand 接口。
      • @Sheldon 啊!谢谢(你的)信息。在那种情况下,我可能会使用标记来指示它是否已被检查。我会附上我的答案。
      【解决方案5】:

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

      一些 MVVM 框架在调用 Execute() 之前确实会检查它。不过,他们不会抛出异常。他们根本不调用Execute(),所以你可能不想自己抛出异常。

      如果CanExecute() 返回 false,您可能会考虑是否根据 Execute() 的行为抛出异常。如果它会做在调用Execute() 之后的任何事情都可以完成的事情,那么抛出是有道理的。如果调用Execute() 的效果不是那么重要,那么静默返回可能更合适。

      【讨论】:

      • 我认为异常可能是一件好事,因为它可能指向有缺陷的代码,其中手动调用了 Execute 而没有先进行任何检查(可能期望它肯定会执行)。
      • 对我来说,这取决于ICommand 将要做什么。如果手动调用,我有一些应该抛出的动作,以及其他可以默默通过的动作。我将对此进行编辑和阐述。
      【解决方案6】:

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

      您可以添加一些标志来检查 CanExecute 是否已被调用,但请注意让它们始终处于命令状态,以免在状态更改时错过或执行不需要的 CanExecute 调用。

      【讨论】:

      • CanExecute 应该被设计为尽可能快,因为它被绑定框架频繁调用。
      • 我确实遇到了这种情况。 CanExecute 花费了相当长的时间,两次调用它并没有改善这一点。一种解决方法是监视源上的属性更改,然后保留一个“CanExecute”变量——尽管这很快变得难以管理,因为可以在与属性更改代码完全不同的上下文和时间调用“CanExecute”。
      【解决方案7】:

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

      【讨论】:

        猜你喜欢
        • 2011-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-28
        • 2018-04-12
        • 1970-01-01
        • 1970-01-01
        • 2015-07-12
        相关资源
        最近更新 更多