内容简介:策略模式的定义是:比如要实现从上海到广州,既可以坐火车,又可以坐高铁,还可以坐飞机。这取决与个人想法。在这里,不同的到达方式就是不同的策略,个人想法就是条件。以计算奖金为例,绩效为S的年终奖是4倍工资,绩效为A的年终奖是3倍工资,绩效为B的年终奖是2倍工资。那么这里奖金取决于两个条件,绩效和薪水。最初编码实现如下:
策略模式的定义是: 定义一系列的算法(这些算法目标一致),把它们一个个封装起来,并且使它们可以相互替换。
比如要实现从上海到广州,既可以坐火车,又可以坐高铁,还可以坐飞机。这取决与个人想法。在这里,不同的到达方式就是不同的策略,个人想法就是条件。
1.计算奖金
以计算奖金为例,绩效为S的年终奖是4倍工资,绩效为A的年终奖是3倍工资,绩效为B的年终奖是2倍工资。那么这里奖金取决于两个条件,绩效和薪水。最初编码实现如下:
const calculateBonus = (performanceLevel, salary) => {
if(performanceLevel === 'S') {
return salary * 4;
}
if(performanceLevel === 'A') {
return salary * 3;
}
if(performanceLevel === 'B') {
return salary * 2;
}
}
复制代码
这段代码十分简单,但存在显而易见的缺点。
- if语句过多,需要涵盖所有的条件。
- 弹性差,如果新增绩效C,那么需要什么calculateBonus的内部实现去修改代码,不符合开放封闭原则。
- 复用性差,计算奖金的算法不能直接复用,除非复制粘贴。
2.使用策略模式
策略模式是指定义一系列的算法,并将它们封装起来,这很符合开闭原则。策略模式的目的就是将算法的使用和算法的实现分离出来。
一个基于策略模式的程序至少由两部分组成。第一个部分是策略类,它封装了具体的算法,并负责计算的具体过程。第二个部分是环境类Context,Context接受客户的请求,随后将请求委托给某一个策略类。要做到这点,Context中需要维持对某个策略对象的引用。
现在使用策略模式来重构以上代码,第一个版本是基于class,第二个版本是基于函数。
2.1基于class
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),将请求委托给策略类。它保存着策略类的引用。
2.2基于函数
上述中,每一个策略都是class,实际上,class也是一个函数。这里,可以直接用函数实现。
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
复制代码
3.多态在策略模式中的体现
通过使用策略模式重构代码,消除了程序中大片的条件语句。所有和奖金相关的逻辑都不在Context中,而是分布在各个策略对象中。Context并没有计算奖金的能力,当它接收到客户的请求时,它将请求委托给某个策略对象计算,计算方法被封装在策略对象内部。当我们发起“获得奖金”的请求时,Context将请求转发给策略类,策略类根据客户参数返回不同的内容,这正是对象多态性的体现,这也体现了策略模式的定义--“它们可以相互替换”。
4.计算小球的缓动动画
我们的目标是编写一个动画类和缓动算法,让小球以各种各样的缓动效果在页面中进行移动。
很明显,缓动算法是一个策略对象,它有几种不同的策略。这些策略函数都接受四个参数:动画开始的位置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()),策略类进行计算并返回结果。
5.更广义的“算法”
策略模式指的是定义一系列的算法,并且把他们封装起来。上述所说的计算奖金和缓动动画的例子都封装了一些策略方法。
从定义上看,策略模式就是用来封装算法的。但如果仅仅将策略模式用来封装算法,有些大材小用。在实际开发中,策略模式也可以用来封装一些的“业务规则”。只要这些业务规则目标一致,并且可以替换,那么就可以用策略模式来封装它们。以使用策略模式来完成表单校验为例。
6.表单校验
<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;
}
}
复制代码
这是一种很常见的思路,和最开始计算奖金一样。缺点也是一样。
6.1使用策略模式重构表单校验
第一步需要把这些校验逻辑封装成策略对象。
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()进行验证。
策略模式优缺点
- 策略模式利用组合、委托和多态等技术和思想,可以有效避免多重选择语句。
- 策略模式通过扩展策略类,对开放封闭原则完全支持,使得它们易于切换和扩展。
- 策略模式的算法可以用在其他地方,避免复制。
- 策略模式利用组合和委托让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
前三点正是开头实现的 计算奖金 函数的缺点。 策略模式有一点缺点,不过并不严重。
- 会增加策略类或者策略对象,增加了复杂度。但是与Context解耦了,这样更便于扩展。
- 使用策略模式,必须了解所有的策略以便选择合适的策略,这是strategies要向客户暴露它的所有实现,不符合最少知识原则。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 设计模式——订阅模式(观察者模式)
- 设计模式-简单工厂、工厂方法模式、抽象工厂模式
- java23种设计模式-门面模式(外观模式)
- 设计模式-享元设计模式
- Java 设计模式之工厂方法模式与抽象工厂模式
- JAVA设计模式之模板方法模式和建造者模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
About Face 3
Alan Cooper、Robert Reimann、David Cronin / John Wiley & Sons / 2007-5-15 / GBP 28.99
* The return of the authoritative bestseller includes all new content relevant to the popularization of how About Face maintains its relevance to new Web technologies such as AJAX and mobile platforms......一起来看看 《About Face 3》 这本书的介绍吧!