定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类
// 计算员工的年终奖金
// S奖金
let performanceS = function () {};
performanceS.prototype.caculate = function (salary) {
return salary * 4;
};
// B奖金
let performanceB = function () {};
performanceB.prototype.caculate = function (salary) {
return salary * 2;
};
let Bonus = function () {
this.salary = null; // 基础薪资
this.strategy = null; // 对应的策略方法
};
Bonus.prototype.setSalary = function (salary) {
this.salary = salary;
};
Bonus.prototype.setStrategy = function (strategy) {
this.strategy = strategy;
};
Bonus.prototype.getBonus = function () {
return this.strategy.caculate(this.salary);
};
let bonus = new Bonus();
bonus.setSalary(1000);
bonus.setStrategy(new performanceS());
console.log(bonus.getBonus());
bonus.setStrategy(new performanceB());
console.log(bonus.getBonus());
JavaScript 中使用策略
let strategies = {
S: function (salary) {
return salary * 4;
},
B: function (salray) {
return salray * 2;
},
};
var calculateBons = function (key, salary) {
return strategies[key](salary);
};
console.log(calculateBons('S', 1000));
console.log(calculateBons('B', 1000));
策略模式对表单的校验
常使用的表单校验
<html>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName" /> 请输入密码:<input type="text" name="password" />
请输入手机号码:<input type="text" name="phoneNumber" />
<button>提交</button>
</form>
<script>
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
if (registerForm.userName.value === '') {
alert('用户名不能为空');
return false;
}
if (registerForm.password.value.length < 6) {
alert('密码长度不能少于 6 位');
return false;
}
if (!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
alert('手机号码格式不正确');
return false;
}
};
</script>
</body>
</html>
使用策略模式
- 重构表单的校验
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|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
},
};
准备实现 Validator 类
Validator 类在这里作为 Context,负责接收用户的请求并委托给 strategy 对象。在给出 Validator 类的代码之前,有必要提前了解用户是如何向 Validator类发送请求的,这有助于我们知道如何去编写 Validator 类的代码。
var validataFunc = function () {
var validator = new Validator(); // 创建一个 validator 对象
/***************添加一些校验规则****************/
validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空');
validator.add(registerForm.password, 'minLength:6', '密码长度不能少于 6 位');
validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');
var errorMsg = validator.start(); // 启动校验获得校验结果
return errorMsg; // 返回校验结果
};
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
var errorMsg = validataFunc(); // 如 果 errorMsg 有确切的返回值,说明未通过校验
if (errorMsg) {
alert(errorMsg);
return false; // 阻止表单提交
}
};
var Validator = function () {
this.cache = []; // 保存校验规则
};
Validator.prototype.add = function (dom, rule, errorMsg) {
var ary = rule.split(':'); // 把 strategy 和参数分开
this.cache.push(function () {
// 把校验的步骤用空函数包装起来,并且放入 cache
var strategy = ary.shift(); // 用户挑选的 strategy
ary.unshift(dom.value); // 把 input 的 value 添加进参数列表
ary.push(errorMsg); // 把 errorMsg 添加进参数列表
return strategies[strategy].apply(dom, ary); // 此处调用重构的校验
});
};
Validator.prototype.start = function () {
for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) {
var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
if (msg) {
// 如果有确切的返回值,说明校验没有通过
return msg;
}
}
};
一个文本有多种校验规则
// 需要校验 validator.add(registerForm.userName, [ { strategy: 'isNonEmpty', errorMsg: '用户名不能为空', }, { strategy: 'minLength:6', errorMsg: '用户名长度不能小于 10 位', }, ]); // 对add代码修改 Validator.prototype.add = function (dom, rules) { var self = this; for (var i = 0, rule; (rule = rules[i++]); ) { (function (rule) { var strategyAry = rule.strategy.split(':'); var errorMsg = rule.errorMsg; self.cache.push(function () { var strategy = strategyAry.shift(); strategyAry.unshift(dom.value); strategyAry.push(errorMsg); return strategies[strategy].apply(dom, strategyAry); }); })(rule); } }; Validator.prototype.start = function () { for (var i = 0, validatorFunc; (validatorFunc = this.cache[i++]); ) { var errorMsg = validatorFunc(); if (errorMsg) { return errorMsg; } } }; // 业务逻辑中的使用 校验的时候的调用 var registerForm = document.getElementById('registerForm'); var validataFunc = function () { var validator = new Validator(); validator.add(registerForm.userName, [ { strategy: 'isNonEmpty', errorMsg: '用户名不能为空', }, { strategy: 'minLength:6', errorMsg: '用户名长度不能小于 10 位', }, ]); validator.add(registerForm.password, [ { strategy: 'minLength:6', errorMsg: '密码长度不能小于 6 位', }, ]); validator.add(registerForm.phoneNumber, [ { strategy: 'isMobile', errorMsg: '手机号码格式不正确', }, ]); var errorMsg = validator.start(); return errorMsg; }; registerForm.onsubmit = function () { var errorMsg = validataFunc(); if (errorMsg) { alert(errorMsg); return false; } };
总结
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它
们易于切换,易于理解,易于扩展。 - 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻
便的替代方案。
参考:JavaScript设计模式与实践