策略模式是什么?
策略模式(Strategy Pattern)指的是定义一系列的算法,把它们一个个封装起来,目的就是将算法的使用与算法的实现分离开来。
我们通常会把算法的含义扩散开来,使策略模式也可以用来封装 一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以 用策略模式来封装它们。
将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成:
-
策略类
策略类封装了具体的算法,并负责具体的计算过程。
-
环境类
接受客户的请求,随后 把请求委托给某一个策略类
策略模式如何实现?
举个栗子:比如公司的年终奖是根据员工的工资和绩效来考核的,年终奖规则如下
- 绩效为A的人,年终奖为工资的4倍
- 绩效为B的人,年终奖为工资的3倍
- 绩效为C的人,年终奖为工资的2倍
现在我们使用一般的编码方式会如下这样编写代码:
// A算法
var performanceA = function(salary) {
return salary * 4;
};
// B算法
var performanceB = function(salary) {
return salary * 3;
};
// C算法
var performanceC = function(salary) {
return salary * 2
};
// 计算年终奖
var calculateBouns = function(level,salary) {
if(level === 'A') {
return performanceA(salary);
}
if(level === 'B') {
return performanceB(salary);
}
if(level === 'C') {
return performanceC(salary);
}
};
// 调用如下
console.log(calculateBouns('A',4500)); // 18000
复用性差
函数体比较庞大,其内包含了很多if-else语句。如果在其他的地方也有类似这样的算法的话,但是规则不一样,我们这些代码不能通用。
缺乏弹性
假如还有D等级的话,那么我们需要添加D算法的同时,在calculateBouns 函数体内添加判断等级D的if语句;
或者想把绩效A的奖金系数改为 5,那我们必须深入 calculateBonus 函数的内部实现,这是违反开放封闭原则的。
我们根据策略模式封装复用的思想,进行改写。
ES5
// 改为策略模式 分成两个函数来写
const strategy = {
'C' : function(experience){// C算法
return 4*experience
},
'A' : function(experience){// A算法
return 3*experience
},
'B' : function(experience){// B算法
return 2*experience
}
}
// getExperience可以复用
function getExperience(strategy, level, experience){
return (level in strategy) ? strategy[level](experience) : experience
}
var C = getExperience(strategy, 'C', 100)
var a = getExperience(strategy, 'A', 100)
ES6
var performanceA = function () {};
performanceA.prototype.calculate = function (salary) {// A算法
return salary * 3;
};
var performanceB = function () {};
performanceB.prototype.calculate = function (salary) {// B算法
return salary * 2;
};
var performanceC = function () {};
performanceC.prototype.calculate = function (salary) {// C算法
return salary * 4;
};
//接下来定义奖金类Bonus:
class Bonus {
constructor() {
this.salary = null; // 原始工资
this.strategy = null; // 绩效等级对应的策略对象
}
setSalary(salary) {// 设置员工的原始工资
this.salary = salary;
}
setStrategy(strategy) {// 设置员工绩效等级对应的策略对象
this.strategy = strategy;
}
getBonus() { // 取得奖金数额
return this.strategy.calculate(this.salary); // 把计算奖金的操作委托给对应的策略对象
}
}
var bonus = new Bonus();
bonus.setSalary(10000);// 设置原始工资
bonus.setStrategy(new performanceS()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:40000
bonus.setStrategy(new performanceA()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:30000
策略模式的应用场景?
应用实例
- jquery插件表单验证代码是策略模式的思想封装的
- Elementui中,基于async-validator库,只需要通过rule属性传入约定的验证规则,即可校验。方便快捷,可复用
项目实战
验证表单
比如有以下几条逻辑:
- 用户名不能为空
- 密码长度不能小于6位。
- 手机号码不能为空、且必须符合格式。
/**HTML结构**/
<form action="/" class="form">
请输入用户名称:<input type="text" name="username">
请输入用户密码:<input type="password" name="password">
请输入用户电话:<input type="text" name="phoneNumber" />
<button>submit</button>
</form>
/**首先定义校验规则**/
const strategies = {
noEmpty: function(value, errMsg){ // 非空
if(value === ''){
return errMsg
}
},
minLength: function(value, length, errMsg){ // 最小长度
if(!value || value.length < length){
return errMsg
}
},
maxLength: function(value, errMsg){// 最大长度
if (value.length > length) {
return errorMsg;
}
}
}
/**接着设置验证器(策略模式)**/
var Validator = function(strategies){// 创建验证器
this.strategies = strategies // 校验规则
this.cache = [] // 存储校验规则
}
Validator.prototype.add = function(dom, rules){// 添加校验规则
rules.forEach(item => {
this.cache.push(() => {
let value = dom.value // 把input 的value 添加进参数列表
let arr = item.rule.split(':') // 把strategy 和参数分开
let name = arr.shift() // 用户挑选的strategy
let params = [value, ...arr, item.errMsg]
return this.strategies[name].apply(dom, params)// apply保证上下文一致
})
})
}
Validator.prototype.validate = function(dom, rules, errMsg){// 校验结果
for(let i = 0, validateFun; validateFun = this.cache[i++];){// 遍历cache里面的校验函数
const message = validateFun() // 开始效验 并取得效验后的返回信息
if(message) return message // 如果有确切的返回值,说明校验没有通过
}
}
/**提交验证**/
var form = document.querySelector("form")
form.onsubmit = function(event){ // 提交表单
event.preventDefault()
const message = validate() // 判断验证结果
if(message){
console.log("验证未通过!",message)
}else{
console.log("验证通过!")
}
}
function validate(){ // 校验函数
const validator = new Validator(strategies) // 实例验证器
// 添加验证规则
validator.add(form.username,
[{rule: 'noEmpty',errMsg: '用户名不能为空!'},{rule: 'minLength:3',errMsg: '用户名长度大于3!'}])
validator.add(form.password,
[{rule: 'minLength:6',errMsg: '密码长度大于6!'},{rule: 'maxLength:10',errMsg: '密码最大长度为10!'}])
return validator.validate()// 进行校验,并返回结果
}
总结
策略模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。
优缺点
优:
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
缺:
- 增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。
- 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。
最后一句
学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。
原文链接:https://juejin.cn/post/7218362171965358139 作者:沉曦