javascript - 多次重复一组方法的设计模式

标签 javascript design-patterns

假设我们正在编写一个电子表格验证函数。用户可以在电子表格中输入多个值,并且有一种方法可以验证这些值是否正确。除了验证它们是否正确之外,还会弹出一个“帮我修复”对话框,询问用户是否要自动修复问题。

出于示例目的,假设我们有以下字段:

  • 事件网址:事件链接。
  • 事件标题:日历事件的名称。
  • 受邀者:应受邀参加事件的用户的逗号分隔电子邮件地址列表。

然后用户可以点击“验证”按钮来检查以下内容:

  • 事件标题与网址中的标题确实匹配。如果没有,他们会看到更新标题的选项。
  • 受邀者都参加了事件。如果没有,则会向用户显示邀请下一位的选项(一次只能执行一次)。

反复执行一组函数的良好编程设计模式是什么?

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

validateEventTitlevalidateInvitees 都应返回 3 个可能值之一:

  • 成功
  • 重试(用户选择使用“帮我修复”按钮。)
  • 错误(用户没有选择“修复它”功能。)

如果其中一个返回 Retry,则应运行整个方法 validateSpreadsheet(例如,如果我们决定让事件标题取决于被邀请者的数量)。

我可以想到函数 validateSpreadsheet 重复其逻辑的几种方法:

  • (A) While 循环
  • (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();

最佳答案

为了简洁、干净的代码,我建议让导致错误的函数实际上抛出错误,如果它们还没有这样做的话。这允许抛出的任何错误立即渗透到顶部包含的 try block :

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
}

关于javascript - 多次重复一组方法的设计模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53992646/

相关文章:

design-patterns - 模式 - 始终由同一方发起的通信

javascript - ExtJs 与 Ext GWT 与 SmartGWT

javascript - 正则表达式替换指令中的 html 标签

javascript - 使用 JavaScript 从 HTML 字符串中提取文本

用于检查整数符号的 Java 设计模式

java - *Manager 类在某些情况下可以维持其存在吗?

javascript - AngularJs 在模式中注入(inject)模板 html

javascript - 文章标签内的元素被重复 10 次,所有这些元素都获得相同的类、id 等

javascript - 构造函数中的 DRY JavaScript 继承

java - 使用哪种设计模式(主动和被动方法)?