![](/img/trans.png)
[英]Should I check an ICommand's CanExecute method before calling Execute from procedural code?
[英]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.