定义
当我们要实现某个功能有多种方案可以选择时,我们可以定义一系列的算法,把他们一个个封装起来,并且使它们可以相互替换,这种解决方案就是策略模式。
例子
以策略模式计算奖金
例如,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。
- 利用一等函数对象实现策略模式
const S = (salary) => {
return salary * 4
}
const A = (salary) => {
return salary * 4
}
const B = (salary) => {
return salary * 4
}
const calculateBonus = (level, salary) => {
return level(salary)
}
console.log(calculateBonus(S, 20000))
console.log(calculateBonus(A, 10000))
- 封装策略对象
// 策略对象
const strategies = {
S(salary){
return salary * 4
},
A(salary){
return salary * 4
},
B(salary){
return salary * 4
},
}
// Context方法
const calculateBonus = (level, salary) => {
return strategies[level](salary)
}
console.log(calculateBonus('S', 20000))
console.log(calculateBonus('A', 10000))
实现缓动动画
编写一个动画类和一些缓动算法,让小球以各种各样的缓动效果在页面中运动
// index.vue
<template>
<button @click="animate('linear')">
linear
</button>
<button @click="animate('easeIn')">
easeIn
</button>
<button @click="animate('strongEaseIn')">
strongEaseIn
</button>
<button @click="animate('strongEaseOut')">
strongEaseOut
</button>
<button @click="animate('sineaseIn')">
sineaseIn
</button>
<button @click="animate('sineaseOut')">
sineaseOut
</button>
<div style="position:relative">
<div style="position:absolute;background:blue;" id="div">我是div</div>
</div>
</template>
<script lang="ts">
import { Animate } from '@/utils/animate'
import { defineComponent } from 'vue'
export default defineComponent({
methods: {
animate(key) {
const div = document.querySelector('#div')
div.style.left = 0;
const animate = new Animate(div)
animate.start('left', 200, 1000, key)
}
}
})
</script>
// animate.js
/**
* 缓动算法策略对象
* t: 动画已消耗的时间
* b: 小球原始位置
* c: 小球目标位置
* d: 动画持续的总时间
*/
const tween = {
linear(t,b,c,d) {
return c*t/d+b;
},
easeIn(t,b,c,d) {
return c*(t/=d)*t+b;
},
strongEaseIn(t,b,c,d) {
return c*(t/=d)*t*t*t*t+b;
},
strongEaseOut(t,b,c,d) {
return c*((t = t/d-1)*t*t*t*t+1)+b;
},
sineaseIn(t,b,c,d) {
return c*(t/=d)*t*t+b;
},
sineaseOut(t,b,c,d) {
return c*((t = t/d-1)*t*t+1)+b;
},
}
const getDate = () => {
return new Date().getTime()
}
// Context类
export class Animate{
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 = getDate(); // 动画启动时间
this.startPos = this.dom.getBoundingClientRect()[propertyName]; // dom节点初始位置
this.propertyName = propertyName; // dom节点需要被改变的css属性名
this.endPos = endPos; // dom节点目标位置
this.duration = duration; // 动画持续时间
this.easing = tween[easing]; // 缓动算法
const timeId = setInterval(() => { // 启动定时器,开始执行动画
if (this.step() === false) { // 如果动画已结束,清除定时器
clearInterval(timeId)
}
}, 19)
}
step() {
const t = getDate(); // 获取当前时间
if (t >= this.startTime + this.duration) { // 动画结束,将小球放置最终位置
this.update(this.endPos);
return false
}
// 动画运行期间,通过缓动函数计算小球当前位置
const pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration);
this.update(pos)
}
update(pos) {
this.dom.style[this.propertyName] = pos + 'px'
}
}
表单校验
注册页面,符合下述要求:
1、用户名不能为空。2、密码长度不能少于6位。3、手机号码必须符合格式。
// index.vue
<template>
<form action="" method="post" ref="registerForm">
请输入用户名:<input type="text" name="userName">
请输入密码:<input type="text" name="password">
请输入手机号码:<input type="text" name="phoneNumber">
<button>提交</button>
</form>
</template>
<script lang="ts">
import { Validator } from '@/utils/validator'
import { defineComponent, onMounted, ref } from 'vue'
export default defineComponent({
setup() {
const registerForm = ref(null)
const validateFunc = (form: any) => {
const validate = new Validator();
validate.add(form.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
},{
strategy: 'minLength:10',
errorMsg: '用户名不能少于10位'
}])
validate.add(form.password, [{
strategy: 'minLength:6',
errorMsg: '密码不能少于6位'
}])
validate.add(form.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}])
const errorMsg = validate.start();
return errorMsg
}
onMounted(() => {
const form = registerForm.value;
form.onsubmit = function(e) {
e.preventDefault();
const msg = validateFunc(form)
if (msg) alert(msg)
}
})
return{
registerForm
}
},
})
</script>
// validator.js
// 策略对象
const strategies = {
isNonEmpty(value, msg) {
if (value === '') return msg;
},
minLength(value, length, msg) {
if (value.length < length) return msg
},
isMobile(value, msg) {
if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) return msg
}
}
// Context类
export class Validator {
constructor() {
this.cache = []
}
add(dom, rules) {
for (let i = 0; i < rules.length; i++) {
const rule = rules[i]
const ary = rule.strategy.split(':')
const msg = rule.errorMsg
this.cache.push(()=> {
const strategy = ary.shift()
ary.unshift(dom.value)
ary.push(msg)
return strategies[strategy].apply(dom, ary)
})
}
}
start() {
for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
var msg = validatorFunc()
if (msg) return msg
}
}
}
优点
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句
- 策略模式提供了开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展
- 策略模式中的算法也可以复用在系统的其他地方,从而避免需要重复的复制粘贴工作
- 在策略模式中利用组合和委托让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案