编辑
2017-07-17
开发
00
请注意,本文编写于 2865 天前,最后修改于 971 天前,其中某些信息可能已经过时。

目录

举个类子
最初的实现
使用组合函数重构
使用策略模式重构
常规的实现
JavaScript版本的策略模式
基于策略模式的表单校验
第一个版本
使用策略模式重构

声明 本篇中代码和部分描述参考和摘录自JavaScript设计模式与实战一书

举个类子

很多公司的年终奖都是根据员工的工资基数和年底绩效来发放的。比如,某个公司的绩效和奖金对应关系如下:

  • S:4倍工资
  • A:3倍工资
  • B:2倍工资

最初的实现

javascript
var calculateBonus = function(level, salary) { if (level === 'S') { return 4 * salary; } if (level === 'A') { return 3 * salary; } if (level === 'B') { return 2 * salary; } } calculateBonus('S', 20000); // 输出80000 calculateBonus('B', 10000); // 输出20000

上面代码的错误显而易见

  • calculateBonus函数比较庞大,包含很多if-else语句
  • calculateBonus函数缺乏弹性,如果要增加一种绩效或者改变某个绩效的奖励系数。我们必须要深入到calculateBonus内部。
  • 算法的复用性差,如果其他地方也要计算奖金,我们的选择只有复制和粘贴。

使用组合函数重构

javascript
var performanceS = function(salary) { return salary * 4; }; var performanceA = function(salary) { return salary * 3; }; var performanceB = function(salary) { return salary * 2; }; var calculateBonus = function(level, salary) { if (level === 'S') { return performanceS(salary); } if (level === 'A') { return performanceA(salary); } if (level === 'B') { return performanceB(salary); } }; calculateBonus('A', 10000); // 输出30000

我们的程序得到了一点改善,但是我们还是没有解决最重要的问题:calculateBonus函数会越来越大,并且随着系统的变化缺乏弹性。

使用策略模式重构

策略模式指的是定义一系列的算法,把它们一个个封装起来。在这里,我们使用策略模式将算法的使用和算法本身分离开来。

常规的实现
javascript
var performanceS = function() {}; performanceS.prototype.calculate = function(salary) { return salary * 4; } var performanceA = function() {}; performanceA.prototype.calculate = function(salary) { return salary * 3; } var performanceB = function() {}; performanceB.prototype.calculate = function(salary) { return salary * 2; } var Bonus = function() { this.salary = null; // 工资基数 this.strategy = null; // 要执行的策略 } /** * 设置工资基数 * @param {Number} salary */ Bonus.prototype.setSalary = function(salary) { this.salary = salary; } /** * 设置策略 * @param {Object} strategy */ Bonus.prototype.setStrategy = function(strategy) { this.strategy = strategy; } /** * 计算奖金 * @return {Number} 奖金数 */ Bonus.prototype.getBonus = function() { return this.strategy.calculate(this.salary); } var bonus = new Bonus(); bonus.setSalary(5000); bonus.setStrategy(new performanceS()); console.log(bonus.getBonus()); // 输出20000 bonus.setStrategy(new performanceA()); console.log(bonus.getBonus()); // 输出15000

可以看到使用策略模式后,代码变得更加清晰,各个类的职责也更加鲜明。

JavaScript版本的策略模式
javascript
var strategies = { 'S': function(salary) { return salary * 4; }, 'A': function(salary) { return salary * 3; }, 'B': function (salary) { return salary * 2; } }; var calculateBonus = function(level, salary) { return strategies[ level ](salary); }; console.log(calculateBonus('S', 10000)); // 输出4000

PeterNoring在他的演讲中曾说过:“在函数作为一等对象的语言中,策略模式是隐形的。strategy就是值为函数的变量。”在JavaScript中,除了用类来封装算法,使用函数当然也是一种选择。这些“算法”被封装到函数中并且四处传递,也就是我们常说的“高阶函数”。根据这一原理,我们对代码可以做进一步的改进。

javascript
var S = function(salary) { return salary * 4; }; var A = function(salary) { return salary * 3; }; var B = function(salary) { return salary * 2; }; var calculateBonus = function(func, salary) { return func(salary); }; console.log(calculateBonus(S, 10000)); // 输出40000

基于策略模式的表单校验

在web项目中,注册、登录、修改用户信息等都离不开表单。在将用户输入的数据提交到后台之前,我们常常会在客户端做一些力所能及的校验。 假设我们正在编写一个注册的页面,注册表单有以下的几条校验规则。

  • 用户名不能为空。
  • 密码长度不能小于6。
  • 手机号码必须符合格式。

第一个版本

先来看看我们通常的做法:

html
<html> <head> <meta charset="utf8"> <title>JavaScript设计模式 - 策略模式</title> <style> .form-group { margin-bottom: 10px; } </style> </head> <body> <div class="wrapper"> <form action="" id="form-signup" method="post"> <div class="form-group"> <label for="username">用户名</label> <input type="text" name="username" /> </div> <div class="form-group"> <label for="password">密码</label> <input type="password" name="password" /> </div> <div class="form-group"> <label for="phone">手机号</label> <input type="tel" name="phone" /> </div> <button type="submit">提交</button> </form> </div> <script type="text/javascript"> var formSignUp = document.getElementById('form-signup'); formSignUp.onsubmit = function() { if (formSignUp.elements.username.value === '') { alert('用户名不能为空'); return false; } if (formSignUp.elements.password.value.length < 6) { alert('密码长度不能小于6'); return false; } if (!/(^1[3|5|8|7][0-9]{9}$)/.test(formSignUp.elements.phone.value)) { alert('非法的手机号'); return false; } return true; } </script> </body> </html>

上面的代码的缺点,跟我们计算奖金的第一个版本的代码一样:

  • formSignUp.onsubmit比较庞大,里面很多if-else语句。
  • formSingUp.onsubmit缺乏弹性,如果增加了一种新的校验规则或者更改某个规则的参数,我们必须深入到函数的内部。
  • 算法的复用性差

使用策略模式重构

根据经验,我们把这些校验规则都封装成策略对象。

javascript
var strategies = { // 空值校验 isNonEmpty: function(value, errorMsg) { if (value === '') { return errorMsg; } }, // 长度校验 minLength: function(value, length, errorMsg) { if (value.length < length) { return errorMsg; } }, // 手机号码格式校验 isMobile: function(value, errorMsg) { if (!/^1[3|5|7|8][0-9]{9}$/.test(value)) { return errorMsg; } } };

接下来我们实现一个Validator类,Validator类负责接收用户委托的strategy对象。我们首先看看它的使用方法。

javascript
var validateFunc = function() { var validator = new Validator(); // 添加验证规则 validator.add(formSignUp.elements.username, 'isNonEmpty', '用户名不能为空'); validator.add(formSignUp.elements.password, 'minLength:6', '密码长度不能小于6'); validator.add(formSignUp.elements.phone, 'isMobile', '非法的手机号'); return validator.start(); }; var formSignUp = document.getElementById('form-signup'); formSignUp.onsubmit = function() { var errorMsg = validateFunc(); if (errorMsg) { alert(errorMsg); return false; // 校验错误则阻止表单的提交 } };

从上面的代码我们看到,我们初始化了一个validator对象,并且通过validator.add()方法添加一些校验规则。validator.add方法接收3各个参数,以下面的代码进行说明。

javascript
validator.add(formSignUp.elements.password, 'minLength:6', '密码长度不能小于6');
  • formSignUp.elements.password密码输入框的的值
  • 'minLength:6'是一个以冒号分割的字符串。冒号的前面minLength代表strategy对象,冒号后面的数字6代表在校验规则中用到的一些参数。'minLength:6'的意思就是formSignUp.elements.password这个值得最小长度为6.如果字符串中不包含冒号,说明校验规则不需要参数,比如'isEmpty'。
  • 第三个参数是当校验未通过的时候返回的错误信息。

当我们往validator对象里添加完规则后,我们会调用validator.start()方法来启动校验。如果校验返回了一个errorMsg,说明校验没有通过,此时需要让formSignUp.onsubmit方法返回false来阻止表单提交。

Validator类的实现:

javascript
var Validator = function() { this.cache = []; }; Validator.prototype.add = function(el, rule, errorMsg) { var arr = rule.split(':'); // 把strategy和参数分开 this.cache.push(function() { // 将校验步骤用空函数包起来,并且放入cache中 var strategy = arr.shift(); // 用户挑选的strategy arr.unshift(el.value); arr.push(errorMsg); return strategies[ strategy ].apply(el, arr); }); }; Validator.prototype.start = function() { for (var i =0, validatorFunc; validatorFunc =this.cache[ i++ ]; ) { var msg = validatorFunc(); if (msg) { return msg; } } };

上面的代码我们只是专注在策略模式的使用上,因此存在小小的缺陷。比如我们要给用户名输入框添加一个新的校验规则只能通过下面的做法。

javascript
validator.add(formSignUp.elements.username, 'isNonEmpty', '用户名不能为空'); // 添加新的规则 validator.add(formSignUp.elements.username, 'minLength:4', '用户名长度不能小于4');

有没有更好的办法呢,留给大家去思考。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:谭三皮

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!