将 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 时必须执行相同的模拟。
有关此的任何设计想法吗?
答案 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的情况下抛出异常。