策略模式

策略模式允许在运行的时候选择算法。你的代码的使用者可以在处理特定任务的时候根据即将要做的事情的上下文来从一些可用的算法中选择一个。

使用策略模式的一个例子是解决表单验证的问题。你可以创建一个validator对象,有一个validate()方法。这个方法被调用时不用区分具体的表单类型,它总是会返回同样的结果——一个没有通过验证的列表和错误信息。

但是根据具体的需要验证的表单和数据,你代码的使用者可以选择进行不同类别的检查。你的validator选择最佳的策略来处理这个任务,然后将具体的数据检查工作交给合适的算法去做。

数据验证示例

假设你有一个下面这样的数据,它可能来自页面上的一个表单,你希望验证它是不是有效的数据:

  1. var data = {
  2. first_name: "Super",
  3. last_name: "Man",
  4. age: "unknown",
  5. username: "o_O"
  6. };

对这个例子中的validator而言,它需要知道哪个是最佳策略,因此你需要先配置它,给它设定好规则以确定哪些是有效的数据。

假设你不需要姓,名字可以接受任何内容,但要求年龄是一个数字,并且用户名只允许包含字母和数字。配置可能是这样的:

  1. validator.config = {
  2. first_name: 'isNonEmpty',
  3. age: 'isNumber',
  4. username: 'isAlphaNum'
  5. };

现在validator对象已经有了用来处理数据的配置,你可以调用validate()方法,然后将验证错误打印到控制台上:

  1. validator.validate(data);
  2. if (validator.hasErrors()) {
  3. console.log(validator.messages.join("\n"));
  4. }

它可能会打印出这样的信息:

  1. Invalid value for *age*, the value can only be a valid number, e.g. 1, 3.14 or 2010
  2. Invalid value for *username*, the value can only contain characters and numbers, no special symbols

现在我们来看一下这个validator是如何实现的。所有可用的用来验证的逻辑都是拥有一个validate()方法的对象,它们还有一行辅助信息用来显示错误信息:

  1. // 验证空值
  2. validator.types.isNonEmpty = {
  3. validate: function (value) {
  4. return value !== "";
  5. },
  6. instructions: "the value cannot be empty"
  7. };
  8. // 验证数字
  9. validator.types.isNumber = {
  10. validate: function (value) {
  11. return !isNaN(value);
  12. },
  13. instructions: "the value can only be a valid number, e.g. 1, 3.14 or 2010"
  14. };
  15. // 验证是否只包含字母和数字
  16. validator.types.isAlphaNum = {
  17. validate: function (value) {
  18. return !/[^a-z0-9]/i.test(value);
  19. },
  20. instructions: "the value can only contain characters and numbers, no special symbols"
  21. };

最后,validator对象的核心是这样的:

  1. var validator = {
  2. // 所有可用的验证类型
  3. types: {},
  4. // 本次验证所有的错误消息
  5. messages: [],
  6. // 本次验证的配置,格式为:
  7. // name: validation type
  8. config: {},
  9. // 接口方法
  10. // `data` 是名值对
  11. validate: function (data) {
  12. var i, msg, type, checker, result_ok;
  13. // 重置所有的错误消息
  14. this.messages = [];
  15. for (i in data) {
  16. if (data.hasOwnProperty(i)) {
  17. type = this.config[i];
  18. checker = this.types[type];
  19. if (!type) {
  20. continue; // 不需要验证
  21. }
  22. if (!checker) { // 没有对应的验证类型
  23. throw {
  24. name: "ValidationError",
  25. message: "No handler to validate type " + type
  26. };
  27. }
  28. result_ok = checker.validate(data[i]);
  29. if (!result_ok) {
  30. msg = "Invalid value for *" + i + "*, " + checker.instructions;
  31. this.messages.push(msg);
  32. }
  33. }
  34. }
  35. return this.hasErrors();
  36. },
  37. // 辅助方法
  38. hasErrors: function () {
  39. return this.messages.length !== 0;
  40. }
  41. };

如你所见,validator对象是通用的,在所有的需要验证的场景下都可以保持这个样子。改进它的办法就是增加更多类型的检查。如果你将它用在很多页面上,那么很快你就会有一个非常好的验证类型的集合。然后在新的使用场景下使用时你需要做的仅仅是配置validator然后调用validate()方法。