简体   繁体   English

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

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

Given that System.Windows.Input.ICommand as 2 primary methods: 鉴于System.Windows.Input.ICommand为2个主要方法:

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

I expect CanExecute(...) to be called in the Command-supported frameworks before Execute(...) is called. 我希望在调用Execute(...)之前在Command支持的框架中调用CanExecute(...)

Internally to my Command Implementation, however, is there any reason to add the CanExecute(...) call inside my Execute(...) implementation ? 但是,在我的Command实现的内部,是否有任何理由在我的Execute(...)实现中添加CanExecute(...)调用?

eg: 例如:

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

This becomes relevant in my testing, as I may mock out some interfaces to support CanExecute , and have to do the same mocks when testing Execute . 这在我的测试中变得相关,因为我可能会模拟一些支持CanExecute的接口,并且在测试Execute时必须执行相同的模拟。

Any design thoughts on this? 对此有何设计想法?

Programmers are notoriously lazy and they will call Execute without calling CanExecute first. 众所周知程序员是懒惰的,他们会先调用Execute而不先调用CanExecute

The ICommand interface is more often used with the WPF binding framework however it is an extremely robust and useful pattern that can be used elsewhere. ICommand接口更常用于WPF绑定框架,但它是一种非常强大且有用的模式,可以在其他地方使用。

I call CanExecute immediately from the Execute method to validate the state of the object. 我立即从Execute方法调用CanExecute来验证对象的状态。 It helps reduce duplicate logic and enforces the use of the CanExecute method (why go through all the effort of whether they can call a method and not bother enforcing it?). 它有助于减少重复逻辑并强制使用CanExecute方法(为什么要经历所有努力,他们是否可以调用方法而不是强制执行它?)。 I don't see a problem with calling CanExecute lots of times because it should be a quick operation anyway. 我没有看到多次调用CanExecute的问题,因为无论如何它应该是一个快速操作。

I do however always document the results of calling an Execute method if the CanExecute method returns false , so that the consumer knows the consequences. 但是,如果CanExecute方法返回false ,我总是记录调用Execute方法的结果,以便消费者知道后果。

I would go one way or the other, but not both. 我会采用其中一种方式,但不是两种方式。

If you expect the user to call CanExecute, then don't call it in Execute. 如果您希望用户调用CanExecute,则不要在Execute中调用它。 You've already set that expectation in your interface and now you have a contract with all the developers that implies this sort of interaction with ICommand. 您已经在界面中设置了这个期望,现在您与所有开发人员签订了合同,这意味着与ICommand进行这种交互。

However, if you're worried about developers not utilizing it properly (as you could rightfully be), then I would suggest removing it from the interface completely, and making it an implementation concern. 但是,如果你担心开发人员没有正确使用它(正如你可以理解的那样),那么我建议将它从界面中完全删除,并使其成为一个实现问题。

Example: 例:

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

This way, you're contract (interface) is clear and concise, and you won't be confused about whether or not CanExecute is getting called twice. 这样,你的合同(界面)清晰简洁,你不会对CanExecute是否被调用两次感到困惑。

However, if you're actually stuck with the interface because you don't control it, another solution could be to store the results and check it like this: 但是,如果你实际上因为你没有控制它而坚持使用界面,那么另一个解决方案可能是存储结果并检查它:

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

I would not be as optimistic as others about adding a call to CanExecute into Execute implementation. 我不会像其他人那样乐观地将CanExecute的调用添加到Execute实现中。 What if your CanExecute execution takes a very long time to complete? 如果您的CanExecute执行需要很长时间才能完成,该怎么办? This would mean that in real-life your user will wait twice that long - once when CanExecute is called by environment, and then when it is called by you. 这意味着在现实生活中,您的用户将等待两倍的长度 - 一旦环境调用CanExecute ,然后由您调用。

You could possibly add some flags to check whether CanExecute has already been called, but be careful to keep them always up to command state not to miss or perform unwanted CanExecute call when the state has changed. 您可以添加一些标志来检查CanExecute是否已被调用,但是要小心保持它们始终处于命令状态,以便在状态发生变化时不会错过或执行不需要的CanExecute调用。

It will probably cause no harm to call CanExecute() within Execute() . Execute()调用CanExecute()可能没有坏处。 Normally Execute() is prevented if CanExecute() would return false since they are usually bound to the UI and not called in your own code. 通常,如果CanExecute()返回false则会阻止Execute()因为它们通常绑定到UI而不是在您自己的代码中调用。 However, nothing forces someone to manually check CanExecute() before calling Execute() , so it's not a bad idea to embed that check. 但是,在调用Execute() CanExecute()之前,没有任何人强迫某人手动检查CanExecute() ,因此嵌入该检查并不是一个坏主意。

Some MVVM frameworks do indeed check it before invoking Execute() . 一些MVVM框架确实在调用Execute()之前检查它。 They don't throw an exception, though. 但是,他们不会抛出异常。 They simply don't call Execute() , so you may not want to throw an exception yourself. 他们根本不调用Execute() ,因此您可能不想自己抛出异常。

You might consider basing whether you throw an exception if CanExecute() returns false on what Execute() would do. 如果CanExecute()Execute()上返回false,您可以考虑是否抛出异常。 If it would do things that would be expected to complete by anything following the call to Execute() , then throwing makes sense. 如果它会在调用Execute()之后Execute()任何预期完成的事情,那么抛出是有意义的。 If the effects of calling Execute() are not so consequential, then maybe silently returning is more appropriate. 如果调用Execute()的效果不那么重要,那么可能默默地返回更合适。

I don't see a problem with adding it. 我没有看到添加它的问题。 As you say, normally the framework will call the CanExecute before the Execute (making a button invisible for example) but a developer may decide to invoke the Execute method for some reason - adding the check will provide a meaningful exception if they do so when they shouldn't. 正如您所说,通常框架将在Execute之前调用CanExecute(例如,使按钮不可见)但开发人员可能出于某种原因决定调用Execute方法 - 添加检查将提供有意义的异常,如果他们这样做的话不应该。

CanExecute is MSFT's easy way(I would say hack) to integrate command with the UI controls such as button. CanExecute是MSFT的简单方法(我会说hack)将命令与UI控件(如按钮)集成。 If we associate a command with a button, FWK can call the CanExecute and enable/disable the button. 如果我们将命令与按钮相关联,FWK可以调用CanExecute并启用/禁用该按钮。 In that sense we should not be calling CanExcute from our Execute method. 从这个意义上讲,我们不应该从Execute方法调用CanExcute。

But if we think from OOP / reusable perspective we can see that ICommand can be used for non-UI purpose as well such as an orchestration / automation code calling my Command. 但是,如果我们从OOP /可重用的角度考虑,我们可以看到ICommand也可以用于非UI目的,例如调用我的Command的编排/自动化代码。 In that scenario it is not necessary that automation will call the CanExecute before calling my Execute().So if we want to have our ICommand implementation working consistently, we need to call CanExecute(). 在那种情况下,自动化在调用我的Execute()之前不必调用CanExecute。因此,如果我们想让我们的ICommand实现一致地工作,我们需要调用CanExecute()。 Yes there is performance problem and we have to adjust with it. 是的,存在性能问题,我们必须对其进行调整。

As per my view, the .Net framework has violated the ISP in this command, like how its violated for ASP.Net Membership provider.Please correct if I am wrong 根据我的观点,.Net框架在此命令中违反了ISP,就像违反ASP.Net成员资格提供者一样。如果我错了,请更正

I know this is an old question, but for reference purposes, you could implement a SafeExecute method inside your generic ICommand implementation, to not have to repeatedly call CanExecute every time you need to manually execute, like this: 我知道这是一个老问题,但是出于参考目的,您可以在通用ICommand实现中实现SafeExecute方法,而不必在每次需要手动执行时重复调用CanExecute ,如下所示:

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

Of course this will not prevent from calling directly Execute() , but at least you follow DRY concept and avoid calling it twice every execution. 当然这不会阻止直接调用Execute() ,但至少你遵循DRY概念并避免每次执行都调用它两次。

The same could be implemented to throwing an exception on CanExecute() returning false. 同样可以实现在CanExecute()上抛出异常返回false。

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

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