内容简介:策略模式的定义是:比如要实现从上海到广州,既可以坐火车,又可以坐高铁,还可以坐飞机。这取决与个人想法。在这里,不同的到达方式就是不同的策略,个人想法就是条件。以计算奖金为例,绩效为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设计模式之模板方法模式和建造者模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Defensive Design for the Web
37signals、Matthew Linderman、Jason Fried / New Riders / 2004-3-2 / GBP 18.99
Let's admit it: Things will go wrong online. No matter how carefully you design a site, no matter how much testing you do, customers still encounter problems. So how do you handle these inevitable bre......一起来看看 《Defensive Design for the Web》 这本书的介绍吧!