策略模式的定义是:定义一系列的算法(这些算法目标一致),把它们一个个封装起来,而且使它们能够相互替换。javascript
好比要实现从上海到广州,既能够坐火车,又能够坐高铁,还能够坐飞机。这取决与我的想法。在这里,不一样的到达方式就是不一样的策略,我的想法就是条件。html
以计算奖金为例,绩效为S的年终奖是4倍工资,绩效为A的年终奖是3倍工资,绩效为B的年终奖是2倍工资。那么这里奖金取决于两个条件,绩效和薪水。最初编码实现以下:java
const calculateBonus = (performanceLevel, salary) => {
if(performanceLevel === 'S') {
return salary * 4;
}
if(performanceLevel === 'A') {
return salary * 3;
}
if(performanceLevel === 'B') {
return salary * 2;
}
}
复制代码
这段代码十分简单,但存在显而易见的缺点。算法
策略模式是指定义一系列的算法,并将它们封装起来,这很符合开闭原则。策略模式的目的就是将算法的使用和算法的实现分离出来。bash
一个基于策略模式的程序至少由两部分组成。第一个部分是策略类,它封装了具体的算法,并负责计算的具体过程。第二个部分是环境类Context,Context接受客户的请求,随后将请求委托给某一个策略类。要作到这点,Context中须要维持对某个策略对象的引用。app
如今使用策略模式来重构以上代码,第一个版本是基于class,第二个版本是基于函数。dom
class PerformanceS {
calculate(salary) {
return salary * 4;
}
}
class PerformanceA {
calculate(salary) {
return salary * 3;
}
}
class PerformanceB {
calculate(salary) {
return salary * 2;
}
}
class Bonus {
constructor(strategy, salary) {
this.strategy = strategy;
this.salary = salary;
}
getBonus() {
if(!this.strategy) {
return -1;
}
return this.strategy.calculate(this.salary);
}
}
const bonus = new Bonus(new PerformanceA(), 2000);
console.log(bonus.getBonus()) // 6000
复制代码
它没有上述的三个缺点。在这里,有三个策略类,分别是PerformanceS、Performance A、PerformanceB。这里的context就是Bonus类,它接受客户的请求(bonus.getBonus),将请求委托给策略类。它保存着策略类的引用。函数
上述中,每个策略都是class,实际上,class也是一个函数。这里,能够直接用函数实现。post
const strategies = {
'S': salary => salary * 4,
'A': salary => salary * 3,
'B': salary => salary * 2,
}
const getBonus = (performanceLevel, salary) => strategies[performanceLevel](salary)
console.log(getBonus('A', 2000)) // 6000
复制代码
经过使用策略模式重构代码,消除了程序中大片的条件语句。全部和奖金相关的逻辑都不在Context中,而是分布在各个策略对象中。Context并无计算奖金的能力,当它接收到客户的请求时,它将请求委托给某个策略对象计算,计算方法被封装在策略对象内部。当咱们发起“得到奖金”的请求时,Context将请求转发给策略类,策略类根据客户参数返回不一样的内容,这正是对象多态性的体现,这也体现了策略模式的定义--“它们能够相互替换”。动画
咱们的目标是编写一个动画类和缓动算法,让小球以各类各样的缓动效果在页面中进行移动。
很明显,缓动算法是一个策略对象,它有几种不一样的策略。这些策略函数都接受四个参数:动画开始的位置s、动画结束的位置e、动画已消耗的时间t、动画总时间d。
const tween = {
linear: (s, e, t, d) => { return e*t/d + s },
easeIn: () => { /* some code */ },
easeOut: () => { /* some code */ },
easeInOut: () => { /* some code */ },
}
复制代码
页面上有一个div元素。
<div id='div' style='position: absolute; left: 0;'></div>
复制代码
如今要让这个div动起来,须要编写一个动画类。
const tween = {
linear: () => { /* some code */ },
easeIn: () => { /* some code */ },
easeOut: () => { /* some code */ },
easeInOut: () => { /* some code */ },
}
class Animation {
constructor(dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null;
this.easing = null;
this.duration = null;
}
// 开始动画
start(propertyName, endPos, duration, easing) {
this.startTime = Date.now();
// 初始化参数,省略其余
const self = this;
// 循环执行动画,若是动画已结束,那么清除定时器
let timer = setInterval(() => {
if(self.step() === false) {
clearInterval(timer);
}
}, 1000/60);
}
// 计算下一次循环到的时候小球位置
step() {
const now = Date.now();
if(now > this.startTime + this.duration) {
return false;
} else {
// 得到小球在本次循环结束时的位置并更新位置
// const pos = this.easing();
// this.update(pos);
}
}
update(pos) {
this.dom.style[propertyName] = pos + 'px';
}
}
复制代码
具体实现略去。这里的Animation类就是环境类Context,当接收到客户的请求(更新小球位置 self.step()),它将请求转发给策略内(this.easing()),策略类进行计算并返回结果。
策略模式指的是定义一系列的算法,而且把他们封装起来。上述所说的计算奖金和缓动动画的例子都封装了一些策略方法。
从定义上看,策略模式就是用来封装算法的。但若是仅仅将策略模式用来封装算法,有些大材小用。在实际开发中,策略模式也能够用来封装一些的“业务规则”。只要这些业务规则目标一致,而且能够替换,那么就能够用策略模式来封装它们。以使用策略模式来完成表单校验为例。
<form action='xxx' id='form' method='post'>
<input type='text' name='username'>
<input type='password' name='passsword'>
<button>提交</button>
</form>
复制代码
验证规则以下:
const form = document.querySelector('form')
form.onsubmit = () => {
if(form.username.value === '') {
alert('用户名不能为空')
return false;
}
if(form.password.value.length < 6) {
alert('密码不能少于6位')
return false;
}
}
复制代码
这是一种很常见的思路,和最开始计算奖金同样。缺点也是同样。
第一步须要把这些校验逻辑封装成策略对象。
const strategies = {
isNonEmpty: (value, errMsg) => {
if(value === '') {
return errMsg
}
},
minLength: (value, errMsg) => {
if(value.length < minLength) {
return errMsg
}
}
}
复制代码
第二步对表单进行校验。
class Validator {
constructor() {
this.rules = [];
}
add(dom, rule, errMsg) {
const arr = rule.split(':');
this.rules.push(() => {
const strategy = arr.shift();
arr.unshift(dom.value);
arr.push(errMsg);
return strategies[strategy].apply(dom, arr);
})
}
start() {
for(let i = 0, validatorFunc; validatorFunc = this.rules[i++];) {
let msg = validatorFunc();
if(msg) {
return msg;
}
}
}
}
const form = document.querySelector('form')
form.onsubmit = (e) => {
e.preventDefault();
const validator = new Validator();
validator.add(form.username, 'isNonEmpty', '用户名不能为空');
validator.add(form.password, 'minLength:6', '密码长度不能小于6位');
const errMsg = validator.start();
if(errMsg) {
alert(errMsg);
return false;
}
}
复制代码
上述例子中,校验逻辑是策略对象,其中包含策略的实现函数。Validator类是Context,用于将客户的请求(表单验证)转发到策略对象进行验证。与计算奖金的Bonus不一样的是,这里并无将验证参数经过构造函数传入,而是经过validator.add传入相关验证参数,经过validator.start()进行验证。
前三点正是开头实现的计算奖金函数的缺点。 策略模式有一点缺点,不过并不严重。