Шаблон проектирования для повторения набора методов несколько раз - PullRequest
0 голосов
/ 01 января 2019

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

В качестве примера, скажем, мыимеют следующие поля:

  • URL события : ссылка на событие.
  • Заголовок события : название события календаря.
  • Приглашенные : список разделенных запятыми адресов электронной почты пользователей, которые должны быть приглашены на событие.

Затем пользователь может нажать «подтвердить»кнопка, которая проверит следующее:

  • То, что заголовок события действительно совпадает с названием в URL.Если это не так, им предоставляется возможность обновить заголовок.
  • Чтобы все приглашенные были на мероприятии.Если это не так, пользователю предлагается вариант приглашения следующего (это делается только один раз за раз).

Что такое хороший шаблон проектирования программирования для выполнения набора функцийснова и снова?

function validateSpreadsheet() {
  validateEventTitle();
  validateInvitees();
}

И validateEventTitle, и validateInvitees должны возвращать одно из 3 возможных значений:

  • Успех
  • Повторить (пользовательрешил использовать кнопку «исправить это для меня».)
  • Ошибка (пользователь не выбрал функцию «исправить это».)

Если один из них возвращает Retry, весь метод validateSpreadsheet должен быть запущен (например, если мы решим, что заголовок события зависит от количества приглашенных).

Я могу представить несколько способов, которыми функция validateSpreadsheet могла бы повторить свою логику:

  • (A) Хотя цикл
  • (B) Рекурсия
  • (C) Массив функций

Я могу придуматьФункция validateEventTitle может сообщать о своем состоянии несколькими способами:

  • (1) она может возвращать перечисление с 3 значениями (успех, повтор, ошибка)
  • (2) это может вызвать исключение в случае повторной попытки и / или ошибки

Я реализовал псевдокод для решения C1 (см. Конец поста), но C1 затрудняет совместное использованиекод между различными методами.Например, если основная часть кода выглядит примерно так:

function validateSpreadsheet() {
  var row = getRow();
  var title = getEventTitle(row);
  validateEventTitle(title, row);
  validateInvitees(row);
}

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

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

Я реализовал рабочий пример решения A2.Похоже, что это работает хорошо, но в значительной степени использует исключения таким образом, что это может запутать нового программиста.Последовательность управления нелегка.

Существует ли уже шаблон проектирования для достижения чего-то подобного?Я хотел бы использовать это, а не изобретать велосипед.


Решение C1 (псевдокод)

function solutionC1() {
  var functions = [ 
    method1, 
    method2 
  ];

  while (true) {
    var result = SUCCESS;

    for (var f in functions) {
      result = f();
      if (result === SUCCESS) {
        continue;  
      } else if (result === REPEAT) {
        break;
      } else {
        return result; // ERROR
      }
    }

    if (result === REPEAT) {
      continue;
    } else {
      return; // SUCCESS
    }
  }
}

Решение B1 (псевдокод)

function solutionB1() {
  var result;

  result = method1();
  if (result === RETRY) {
    return solutionB1();
  } else if (isError(result)) {
    return result;
  }

  result = method2();
  if (result === RETRY) {
    return solutionB1();
  } else if (isError(result)) {
    return result;
  }
}

Решение A2 (Работа с юнит-тестами)

function solutionA2() {
  while (true) {
    try {
      // these two lines could be extracted into their own method to hide the looping mechanism
      method1();
      method2();
    } catch(error) {
      if (error == REPEAT) {
        continue;
      } else {
        return error;
      }
    }
    break;
  }
}

var REPEAT = "REPEAT";
var method1Exceptions = [];
var method2Exceptions = [];
var results = [];

function unitTests() {
  // no errors
  method1Exceptions = [];
  method2Exceptions = [];
  results = [];
  solutionA2();
  if (results.join(" ") !== "m1 m2") { throw "assertionFailure"; }
  
  // method1 error
  method1Exceptions = ["a"];
  method2Exceptions = ["b"];
  results = [];
  solutionA2();
  if (results.join(" ") !== "m1:a") { throw "assertionFailure"; }
  
  // method1 repeat with error
  method1Exceptions = [REPEAT, "a"];
  method2Exceptions = ["b"];
  results = [];
  solutionA2();
  if (results.join(" ") !== "m1:REPEAT m1:a") { throw "assertionFailure"; }

  // method1 multiple repeat
  method1Exceptions = [REPEAT, REPEAT, REPEAT, "a"];
  method2Exceptions = ["b"];
  results = [];
  solutionA2();
  if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1:a") { throw "assertionFailure"; }
  
  // method1 multiple repeat, method2 repeat with errors
  method1Exceptions = [REPEAT, REPEAT, REPEAT];
  method2Exceptions = [REPEAT, REPEAT, "b"];
  results = [];
  solutionA2();
  if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1 m2:REPEAT m1 m2:REPEAT m1 m2:b") { throw "assertionFailure"; }

  // method1 multiple repeat, method2 repeat with no errors
  method1Exceptions = [REPEAT, REPEAT, REPEAT];
  method2Exceptions = [REPEAT, REPEAT];
  results = [];
  solutionA2();
  if (results.join(" ") !== "m1:REPEAT m1:REPEAT m1:REPEAT m1 m2:REPEAT m1 m2:REPEAT m1 m2") { throw "assertionFailure"; }
  
   // [REPEAT, "Test"];
}

function method1() {
  // in reality, this method would do something useful, and return either success, retry, or an exception. To simulate that for unit testing, we use an array.
  var exception = method1Exceptions.shift();
  if (typeof exception !== "undefined") {
    results.push("m1:" + exception);
    throw exception;
  } else {
    results.push("m1");
  }
}

function method2() {
  // in reality, this method would do something useful, and return either success, retry, or an exception. To simulate that for unit testing, we use an array.
  var exception = method2Exceptions.shift();
  if (typeof exception !== "undefined") {
    results.push("m2:" + exception);
    throw exception;
  } else {
    results.push("m2");
  }
}

unitTests();

1 Ответ

0 голосов
/ 01 января 2019

Для краткого, чистого кода, я бы предложил, чтобы функции, которые приводят к ошибкам, фактически генерировали ошибки, если они этого еще не делают.Это позволяет при возникновении любых ошибок, которые сразу же попадают вверх, в содержащий блок try:

const fns = [
  method1,
  method2
];
// If the methods return errors but don't throw them, pipe them through isError first:
const fnsThatThrow = fns.map(fn => () => {
  const result = fn();
  if (isError(result)) {
    throw new Error(result);
  }
  return result;
});

Затем все, что вам нужно сделать, это проверить, приводит ли вызов любой из функций к REPEAT (вв этом случае рекурсивно вызывайте validateSpreadsheet), чего можно достичь с помощью Array.prototype.some:

function validateSpreadsheet() {
  if (fnsThatThrow.some(fn => fn() === REPEAT)) {
    return validateSpreadsheet();
  }
}

try {
  validateSpreadsheet();
} catch(e) {
  // handle errors
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...