JavaScript设计模式 -- 策略模式
作者: DocWhite白先生
一. 概念
策略模式,通俗易懂,就是字面意思,无论是生活还是编程,在实现一个目的的时候通常会有很多策略能达成目的,而策略模式存在的意义在于可以随意切换某种策略但依然保证能达到期望的目的。也就是:
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
二. 简单例子
使用策略模式设计一个计算奖金的程序。
很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为 S 的人年 终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资,其他为一倍工资。假设财 务部要求我们提供一段代码,来方便他们计算员工的年终奖。
先不考虑策略模式,一个最简单的实现是这样:
function calculateBonus(performanceLevel, salary) {
// 当然也可以用 if else 语句
switch (performanceLevel){
case "S":
return salary * 4;
case "A":
return salary * 3;
case "B":
return salary * 2;
default:
return salary;
}
}
可以发现这段代码存在一个明显的缺陷,架设评级增多,出现新的C,D级,并且S,A,B级的奖金倍数出现变动,这段代码必须重写。
这意味着这个
(1)calculateBonus函数比较庞大。
(2)函数缺乏弹性。
(3)算法的复用性差。
1. 使用组合函数重构代码
上面代码出现的问题,最简单解决方式就是使用组合函数重构代码。将每一种评级的计算方法封装成单一的函数,使之符合“单一职责原则”:
function performanceS(salary){
return salary * 4;
}
...
function calculateBonus(performance, salary) {
switch (performanceLevel){
case "S":
return performanceS(salary);
case "A":
return performanceA(salary);
case "B":
return performanceB(salary);
default:
return performanceDefault(salary);
}
}
这样处理之后程序得到一定的改善,但是这种改善并没有解决根本问题,calculateBonus函数可能会越来越庞大。
先使用传统的面向对象的思路去重构一下上面的代码以符合策略模式,将每一种计算策略封装为一个类,统一提供一个计算接口,同时也把计算奖金的calculateBonus修改为奖金类(Bonus):
function performanceS(){}
performanceS.prototype.calculate = function(salary) {
return salary * 4;
}
...
performanceDefault.prototype.calculate = function(salary) {
return salary;
}
function Bonus () {
this.salary = null;
this.strategy = null;
}
Bonus.prototype.setSalary = function (salary) {
return this.salary = salary;
}
Bonus.prototype.setStrategy = function ( strategy) {
return this.strategy = strategy;
}
Bonus.prototype.calculateBonus = function(){
if( !this.strategy){return null;}
return this.strategy.calculate(this.salary);
}
// Usage
const bonus = new Bonus();
bonus.setSalary(10000);
bonus.setStrategy(new performanceS());
bonus.calculateBonus(); // 40000
可以看到,当使用策略类的时候我们使用的是其提供的统一对外接口,在Bonus类中获得计算结果时,它自身没有实现计算算法,而是将其委托给了策略类封装的计算算法。可以看到这样做之后代码变得更加清晰以及职责鲜明,但是这段代码是对传统面向对象编程的模仿,然而JavaScript并不是一门传统的强类型语言。
同时上面这个例子也能清晰的看到策略模式里面存在多态的应用。当发起计算奖金的请求时候,这个计算并没有在Bonus类中实现,而是委托到策略类中,由策略类自己实现计算方法,同时这个计算方法还可以相互替代,这正是对象多态的体现。
2. JavaScript版本的策略模式
由于JavaScript语言的特性,我们不需要定义类就能直接获得对象,所以何不直接定义一个策略对象:
var strategies = {
"A": function( salary ){
return salary * 4;
},
"B": function( salary ){
return salary * 2;
}
// ...
"default": function(salary) {
return salary;
}
}
function calculateBonus(strategyLevel, salary){
return strategy[ strategyLevel ](salary);
}
calculateBonus("S" , 10000); // 40000
三. 策略模式在JavaScript中的应用
在前端开发中,最常出现的场景是我们需要动态的调整Dom元素的css样式(当然是纯css比较难处理的情况),例如动画。
用JavaScript去控制动画,通常需要注意的点如下:
- 运动开始位置
- 运动结束位置
- 已消耗时间
- 运动持续总时间
要模拟一个动画,只需要设置定时器,让div每隔一定间隔往结束位置运动一段距离即可:
下面这些算法都接收4个参数,这4个参数的意义分别是动画已经消耗的时间、dom的原始位置、目标位置、动画持续的总时间,结果则是当前位置。
const tween = {
linear: function(t, b, c, d) {
return c * ( t / d) + b;
},
easeIn: function(t, b, c, d) {
return c * ( t /= d) *t + b;
},
strongEaseIn: function(t, b, c, d) {
return c* ( t /= d ) * t * t * t * t + b;
},
strongEaseOut: function(t, b, c, d){
return c * ( ( t = t / d - 1) * t * t * t * t + 1 ) + b;
},
sineaseOut: function(t,b,c,d){
return c * ( ( t = t / d - 1) * t * t + 1 ) + b;
}
}
参照jQuery的思想,定义一个Animate类,构造函数接收一个dom作为参数:
function Animate (dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null;
this.easing = null;
this.duration = null;
}
它的原型方法start负责启动动画:
Animate.prototype.start = function(propertyName, endPos, duration, easing) {
this.startTime = newDate();
this.startPos = this.dom.getBoundingClientRect()[ propertyName ];
this.propertyName = propertyName;
this.endPos = endPos;
this.duration = duration;
this.easing = tween[easing];
var self = this;
var timer = serInterval( function() {
if(self.step() === false){
clearInterval( timer );
}
}, 19)
}
// 接下来就是代表每一帧的运动函数
Animate.prototype.step = function() {
var timer = new Date();
if( timer >= ( this.startTime + this.duration) ){
this.update(this.endPos); // 修正结束时的位置
return false;
}
var pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this,duration);
this.update( pos );
}
Animate.prototype.step = function(pos) {
this.dom.style[ this.propertyName ] = pos + 'px';
}
开始测试:
var dom = document.getElementById( 'div' );
var animate = new Animate( div );
animate.start('left', 0, 1000, 'easeIn');
上面这个策略模式的例子表明,这些算法都可以轻易得被另外一个算法替代,这是策略模式的经典应用之一。
本文介绍了JavaScript设计模式中的策略模式,通过一个计算奖金的程序为例,展示了如何使用策略模式优化代码,提高代码的灵活性和复用性。文章讨论了如何从传统面向对象的重构到JavaScript特有的策略模式实现,并探讨了策略模式在前端开发中动态调整DOM元素CSS样式和动画控制的应用。

1164

被折叠的 条评论
为什么被折叠?



