在编写程序时常常会遇到这样的情况:如果条件A成立,采用处理方式A,如果条件B成立,则采用处理方式B...而这几种处理方式是类似的。策略模式就是针对这种问题提出的。
策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
一个非常经典的例子:计算员工工资
如果员工的绩效达到S级标准,1.6倍基础工资;A级,1.4倍基础工资;B级1.2倍基础工资;C级,基础工资。
最容易想到的解决方式:if else堆砌式代码
function getSalary(grade, base) {
if (grade === 'S') {
return base * 1.6;
}
if (grade === 'A') {
return base * 1.4;
}
if (grade === 'B') {
return base * 1.2;
}
if (grade === 'C') {
return base;
}
}
复制代码
这样看似简单,其实不然。如果企业迈入了一个更为繁荣的阶段,修改达到绩效标准的工资,或者需要再添加绩效标准,就需要大幅度的改动;而且采用这种设计,存在大量分支,导致代码非常臃肿。这样的设计不利于修改和维护
使用策略模式解决上述问题
策略模式需要封装一系列可互相替换的算法,在上述问题中,根据绩效计算工资的算法是可以相互替换的
首先定义根据绩效计算工资的函数
let performanceS = function() {}
performanceS.prototype.getSalary = function(base) {
return base * 1.6;
}
let performanceA = function() {}
performanceA.prototype.getSalary = function(base) {
return base * 1.4;
}
let performanceB = function() {}
performanceB.prototype.getSalary = function(base) {
return base * 1.2;
}
let performanceC = function() {}
performanceC.prototype.getSalary = function(base) {
return base;
}
复制代码
然后定义工资类,使得能够根据绩效来选择计算工资的方法
let Salary = function() {
this.base = null;
this.strategy = null;
}
Salary.prototype.setBase = function(salary) {
this.salary = salary;
}
Salary.prototype.setStrategy = functon(strategy) {
this.strategy = strategy;
}
Salary.prototype.getSalary = function() {
return this.strategy.setSalary(this.base);
}
复制代码
调用上面编写的计算工资的方法
// 假设某个企业的销售人员的基础工资是6000元,其绩效达到S级标准
let salary = new Salary();
salary.setBase(6000);
salary.setStrategy(new performanceS());
console.log(salary.getSalary());
复制代码
以上设计的主要思想是根据上下文来将事件交给其他类来处理,如果需要增加某个绩效标准,只需要增加一个新的performance类,其他的代码不需要改动。
策略模式在前端中的应用举例:表单验证
// form_check.js
let 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;
}
}
};
// 校验类
function Validator() {
this.cache = []; // 存储校验规则
}
// 添加校验规则
Validator.prototype.add = function(dom, rules) {
let self = this;
for (let i = 0, rule; rule = rules[i++];) {
(function() {
let strategyAry = rule.strategy.split(':'); // 针对长度验证的操作
let errorMsg = rule.errorMsg;
self.cache.push(function() {
let strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(dom, strategyAry);
});
})(rule);
}
};
// 验证是否满足所有校验规则
Validator.prototype.start = function() {
for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
let msg = validatorFunc();
if (msg) {
return msg;
}
}
};
复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>#title</title>
</head>
<body>
<form action="" id='registerForm' method="post">
<input type="text" placeholder="请输入用户名" name='username'/>
<input type="password" placeholder="请输入密码" name='password'/>
<input type="text" placeholder="请输入手机号" name='phonenum'>
<button>submit</button>
</form>
</body>
<script src="form_check.js"></script>
<script src="index.js"></script>
</html>
复制代码
// index.js
let registerForm = document.getElementById('registerForm');
let validataFunc = function () {
let validator = new Validator();
validator.add(registerForm.username, [
{
strategy: 'isNonEmpty',
errorMsg: 'username should not be empty!'
},
{
strategy: 'minLength:6',
errorMsg: 'username\'s length should be more than 10!'
}
]);
validator.add(registerForm.password, [
{
strategy: 'minLength:6',
errorMsg: 'password\'s length should be more than 10!'
}
]);
validator.add(registerForm.phonenum, [
{
strategy: 'isMobile',
errorMsg: 'wrong format phone num!'
}
]);
let errorMsg = validator.start();
return errorMsg;
};
registerForm.onsubmit = function() {
let errorMsg = validataFunc();
if (errorMsg) {
alert(errorMsg);
return false;
}
return true;
};
复制代码
策略模式的优势在于满足了开闭原则,避免了if else的堆砌式的代码;其缺点是需要构建许多策略类,但是这比堆砌式的if else要好很多;如果要调用某种策略,必须知道所有的策略的运行方式,相当于暴露了其实现方式。