Шаблоны для работы с функциями, которые могут иметь разные результаты - PullRequest
4 голосов
/ 01 июня 2010

Предположим, у вас есть метод для объекта, который при некотором входе изменяет состояние объекта , если , вход проверяется в соответствии с некоторой сложной логикой.

Теперь предположим, что когда входные данные не проверяются, это может быть связано с несколькими разными вещами, с каждой из которых мы хотели бы иметь дело по-разному.

Я уверен, что многие из вас думают: для этого есть исключения! Я тоже об этом думал.

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

Если бы была возможна только одна интерпретация, я мог бы просто выбрать возвращаемое логическое значение, указывающее, привела ли операция к изменению состояния или нет, и соответствующий ответ, если это не так.

Конечно, существует также возможность вернуть код состояния, который клиент может затем интерпретировать или нет. Мне это тоже не очень нравится, потому что в кодах состояния нет ничего семантического.

Решение, которое я имею до сих пор, состоит в том, чтобы всегда проверять каждую возможную ситуацию, которую я могу обработать до Я вызываю метод, который затем возвращает логическое значение, чтобы сообщить клиенту, изменилось ли состояние объекта. Это оставляет мне возможность обрабатывать как можно меньше или столько возможных ситуаций, сколько я хочу, в зависимости от контекста, в котором я нахожусь. Это также дает преимущество, заключающееся в упрощении написания метода, который я вызываю. Недостаток в том, что в клиентском коде достаточно много дублирования, где бы я ни вызывал метод.

Какое из этих решений вы предпочитаете и почему?

Какие еще шаблоны используют люди для обеспечения значимой обратной связи от функций? Я знаю, что некоторые языки поддерживают множественные возвращаемые значения, и если бы у меня была такая опция, я бы ее предпочел.

Ответы [ 5 ]

1 голос
/ 01 июня 2010

Я думаю, что это тот случай, когда вы используете примитивы, когда вам будет лучше с объектами. Я обрабатываю это в своем собственном коде, возвращая статус objects . Эти объекты позволяют вам инкапсулировать как простой логический флаг «он работал / не работает», так и предоставляют дополнительную информацию о том, почему произошел сбой, или другие соответствующие метаданные, такие как описание некоторого изменения состояния.

Это похоже на идею 'tracker', описанную в mdma's answer . Насколько глубоко анализируется объект статуса, зависит от клиента. Клиент, который не заботится о деталях, может просто проверить метод status.was_successful(). Разница в том, что я бы возвращал «трекер» напрямую, а не передавал его как ссылку. Это значительно упрощает интерфейс вызова.

Возврат объекта также эффективно обрабатывает любую потребность в нескольких возвращаемых параметрах, поскольку вы можете просто инкапсулировать все, что вам нужно, в один объект состояния.

1 голос
/ 01 июня 2010

Возвращаемое значение - не единственное выходное значение метода, вы можете иметь параметры «out» или передавать другие объекты в качестве ссылок. Например, помимо ввода для проверки, вы можете, например, передать в метод дополнительный объект - «трекер», который метод заполняет сведениями о достоверности ввода и деталями изменения состояния.

Вы все еще можете сохранять логическое значение как общий флаг успеха / неудачи. Некоторые клиенты могут не заботиться о деталях и передают нулевую ссылку для трекера, полагаясь только на возвращаемое значение. Другие клиенты могут хотеть все детали, и поэтому будут использовать только трекер, чтобы определить, изменилось ли состояние.

Важно отметить, что, разрешив клиенту передавать данные в трекере, клиент может указать, сколько деталей нужно собрать, и решить, что делать с информацией. Некоторые из них могут быть дорогими для вычислений или слишком большими для хранения памяти. Наличие трекера в качестве параметра, а не просто возвращаемого значения, позволяет клиенту предоставлять наиболее подходящую реализацию. Класс трекера может включать флаги и другие сигналы, которые устанавливает клиент, которые указывают валидатору, какую информацию клиент хочет получить от валидации.

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

   interface ValidationResult  {
      List<ValidationError> validationErrors();  
      boolean isValid();      
      List<StateChange> stateChanges();
      boolean hasChanged();
   }

   public class AbstractValidationResult extends ValidationResult {
      // provides setters for properties. Make setter available to
      // the StateChangeValidator, but hides these from the ValidationResult
      // interface that general clients use.
      public void setValid(boolean valid) {
          this.valid = valid;
      }
   }

   class DefaultValidationResult : AbstractValidationResult {
       // default implementation
   }

   // your input validationg/state changing class
   class StateChangeValidator 
   {
        // convenience method
        public ValidationResult setState(Input intput)
        {
           return setState(input, new DefaultValidationResult());
        }

        // implementation allows clients to specify how the validation is handled.
        public ValidationResult setState(Input input, AbstactValidationResult result)
        {
           validate(input, result);
           changeState(input);
           return result;
        }

        // just validation - no actual state change
        public ValidationResult validate(Input input, AbstractValidationResult result)
        {
           result.setvalid(!hasErrors);
           return result;               
        }
   }

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

Также может быть полезно выделить метод «validate» для вашего объекта, который проверяет входные данные, но фактически не вносит изменения в состояние. Этот шаблон может быть полезен при предварительной проверке ввода, например, при включении кнопки ОК в пользовательском интерфейсе.

0 голосов
/ 01 июня 2010

По-моему, можно использовать лямбда / замыкание (в зависимости от того, что вам нужно). В разговоре вот что вы делаете.

dic at: #foo ifAbsent: [ 'do something interesting' ]

Здесь вы можете видеть, что когда вы не можете найти что-то в своем словаре, вы оцениваете блок. Это может относиться к вашей проблеме. Когда вы не проверяете ввод, спрашивайте отправителя, что делать, оценивая закрытие.

Просто, не правда ли?

0 голосов
/ 01 июня 2010

почему бы не использовать один из шаблонов стратегий или шаблонов. Я бы выбрал метод Template. Скажем, у вашей функции сложная логика, а входным параметром является интерфейс со свойствами состояния и описания. Так что после возврата функции вы получаете входной объект с заполненной информацией о состоянии и описании. Кроме того, этот интерфейс может быть реализован различными «входными» классами.

0 голосов
/ 01 июня 2010

Код состояния может быть семантическим, если вы не используете простые числа и заменяете их какими-то константами. Например, при загрузке файлов в PHP вы имеете дело с явными константами, такими как UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE и так далее. Конечно, это становится грязно, когда вы используете простые числа, такие как 1, 2, 3, потому что вы не знаете, что они значат сразу. Но константы обеспечивают элегантный способ для кода состояния что-то значить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...