ICommand - Должен ли я вызвать CanExecute в Execute?

Учитывая, что System.Windows.Input.ICommand как 2 основных метода:

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

Я ожидаю, что CanExecute (...) вызывается в поддерживаемых командами фреймах до вызова функции Execute (...).

Внутри моей реализации команды, однако, есть ли причина добавить вызов CanExecute (...) в мою реализацию Execute (...)?

например:.

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

Это становится актуальным в моем тестировании, так как я могу издеваться над некоторыми интерфейсами для поддержки CanExecute и должен выполнять те же макеты при тестировании Execute.

Любые дизайнерские мысли по этому поводу?

Ответ 1

Программисты, как известно, ленивы, и они вызовут Execute, не вызывая сначала CanExecute.

Интерфейс ICommand чаще используется с каркасом привязки WPF, однако он является чрезвычайно надежным и полезным шаблоном, который можно использовать в другом месте.

Я вызываю CanExecute непосредственно из метода Execute для проверки состояния объекта. Это помогает уменьшить дублирующую логику и применяет метод CanExecute (зачем проходить через все усилия, можно ли вызвать метод и не беспокоить его применение?). Я не вижу проблемы с вызовом CanExecute много раз, потому что в любом случае это должна быть быстрая операция.

Однако я всегда документирую результаты вызова метода Execute, если метод CanExecute возвращает false, чтобы потребитель знал о последствиях.

Ответ 2

Я бы пошел так или иначе, но не оба.

Если вы ожидаете, что пользователь вызовет 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); 
    }
}

Ответ 3

Я бы не был так оптимистичен, как другие о добавлении вызова CanExecute в Execute. Что делать, если выполнение CanExecute занимает очень много времени? Это будет означать, что в реальной жизни ваш пользователь будет ждать в два раза больше - один раз, когда CanExecute вызывается средой, а затем, когда она вызывается вами.

Вы могли бы добавить несколько флагов, чтобы проверить, был ли уже вызван CanExecute, но будьте осторожны, чтобы они всегда были в состоянии команды, чтобы не пропустить или выполнить нежелательный вызов CanExecute, когда состояние изменилось.

Ответ 4

Это, вероятно, не вызовет вреда для вызова CanExecute() внутри Execute(). Обычно Execute() предотвращается, если CanExecute() возвращает false, поскольку они обычно связаны с пользовательским интерфейсом и не вызываются в вашем собственном коде. Тем не менее, ничто не заставляет кого-то вручную проверять CanExecute() перед вызовом Execute(), поэтому неплохо вставить эту проверку.

Некоторые MVVM-структуры действительно проверяют его перед вызовом Execute(). Однако они не исключают исключение. Они просто не звонят Execute(), поэтому вы можете не захотеть выбросить исключение самостоятельно.

Вы можете подумать о том, генерируете ли вы исключение, если CanExecute() возвращает false, что бы сделать Execute(). Если он будет делать то, что, как ожидается, будет завершено чем-либо после вызова Execute(), тогда бросание имеет смысл. Если эффекты вызова Execute() не так косвенны, то возможно более тихое возвращение является более подходящим.

Ответ 5

Я не вижу проблемы с его добавлением. Как вы говорите, обычно инфраструктура будет вызывать CanExecute до Execute (например, сделать невидимую кнопку), но разработчик может по какой-то причине решить вызывать метод Execute - добавление проверки предоставит осмысленное исключение, если они это сделают, когда они не должно.

Ответ 6

CanExecute - это простой способ MSFT (я бы сказал, взломать), чтобы интегрировать команду с элементами управления пользовательского интерфейса, такими как кнопка. Если мы связываем команду с кнопкой, FWK может вызывать CanExecute и включать/отключать кнопку. В этом смысле мы не должны вызывать CanExcute из нашего метода Execute.

Но если мы думаем из перспективы ООП/многократного использования, мы можем видеть, что ICommand может использоваться и для целей, отличных от UI, а также для кода оркестровки/автоматизации, вызывающего мою команду. В этом случае нет необходимости, чтобы автоматизация вызывала CanExecute перед вызовом моего Execute(). Поэтому, если мы хотим, чтобы наша работа ICommand работала последовательно, нам нужно вызвать CanExecute(). Да, есть проблема с производительностью, и мы должны приспособиться к ней.

В соответствии с моим представлением инфраструктура .Net нарушила ISP в этой команде, например, как ее нарушили для провайдера членства ASP.Net. Пожалуйста, исправьте, если я ошибаюсь

Ответ 7

Я знаю, что это старый вопрос, но для справочных целей вы можете внедрить метод SafeExecute внутри вашей универсальной реализации ICommand, чтобы не требовать многократного вызова CanExecute каждый раз, когда вам нужно выполнить вручную, например это:

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

Конечно, это не помешает прямому вызову Execute(), но, по крайней мере, вы следуете концепции DRY и не вызываете ее дважды каждый раз.

То же самое можно было бы реализовать для исключения исключения на CanExecute(), возвращающего false.