学习JavaScript设计模式(策略模式)


Posted in Javascript onNovember 26, 2015

何为策略?比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路。
1、策略模式的定义

如果没有时间但是不在乎钱,可以选择坐飞机。
如果没有钱,可以选择坐大巴或者火车。
如果再穷一点,可以选择骑自行车。
在程序设计中,我们也常常遇到类似的情况,要实现某一个功能有多种方案可以选择。比如一个压缩文件的程序,既可以选择zip算法,也可以选择gzip算法。

定义:策略模式定义一系列的算法,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算饭的客户.

策略模式有着广泛的应用。本节我们就以年终奖的计算为例进行介绍。

2、年终奖实例

很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资。假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖。

1). 最初的代码实现

我们可以编写一个名为calculateBonus的函数来计算每个人的奖金数额。很显然,calculateBonus函数要正确工作,就需要接收两个参数:员工的工资数额和他的绩效考核等级。代码如下:

var calculateBonus = function( performanceLevel, salary ){
 if ( performanceLevel === 'S' ){
 return salary * 4;
 }

 if ( performanceLevel === 'A' ){
 return salary * 3;
 }

 if ( performanceLevel === 'B' ){
 return salary * 2;
 }
};
calculateBonus( 'B', 20000 ); // 输出:40000
calculateBonus( 'S', 6000 ); // 输出:24000

可以发现,这段代码十分简单,但是存在着显而易见的缺点。

calculateBonus函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有的逻辑分支。

calculateBonus函数缺乏弹性,如果增加了一种新的绩效等级C,或者想把绩效S的奖金系数改为5,那我们必须深入calculateBonus函数的内部实现,这是违反开放-封闭原则的。

算法的复用性差,如果在程序的其他地方需要重用这些计算奖金的算法呢?我们的选择只有复制和粘贴。因此,我们需要重构这段代码。

2). 使用组合函数重构代码

一般最容易想到的办法就是使用组合函数来重构它,我们把各种算法封装到一个个的小函数里面,这些小函数有着良好的命名,可以一目了然地知道它对应着哪种算法,它们也可以被复用在程序的其他地方。代码如下:

var performanceS = function( salary ){
 return salary * 4;
};

var performanceA = function( salary ){
 return salary * 3;
};

var performanceB = function( salary ){
 return salary * 2;
};

var calculateBonus = function( performanceLevel, salary ){

 if ( performanceLevel === 'S' ){
 return performanceS( salary );
 }

 if ( performanceLevel === 'A' ){
 return performanceA( salary );
 }

 if ( performanceLevel === 'B' ){
 return performanceB( salary );
 }

};
calculateBonus( 'A' , 10000 ); // 输出:30000

目前,我们的程序得到了一定的改善,但这种改善非常有限,我们依然没有解决最重要的问题:calculateBonus函数有可能越来越庞大,而且在系统变化的时候缺乏弹性。

3). 使用策略模式重构代码

经过思考,我们想到了更好的办法——使用策略模式来重构代码。策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来。

在这个例子里,算法的使用方式是不变的,都是根据某个算法取得计算后的奖金数额。而算法的实现是各异和变化的,每种绩效对应着不同的计算规则。

一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明Context中要维持对某个策略对象的引用。

现在用策略模式来重构上面的代码。第一个版本是模仿传统面向对象语言中的实现。我们先把每种绩效的计算规则都封装在对应的策略类里面:

var performanceS = function(){};

performanceS.prototype.calculate = function( salary ){
 return salary * 4;
};

var performanceA = function(){};

performanceA.prototype.calculate = function( salary ){
 return salary * 3;
};

var performanceB = function(){};

performanceB.prototype.calculate = function( salary ){
 return salary * 2;
};

接下来定义奖金类Bonus:

var Bonus = function(){
 this.salary = null; //原始工资
 this.strategy = null; //绩效等级对应的策略对象
};

Bonus.prototype.setSalary = function( salary ){
 this.salary = salary; //设置员工的原始工资
};

Bonus.prototype.setStrategy = function( strategy ){
 this.strategy = strategy; //设置员工绩效等级对应的策略对象
};

Bonus.prototype.getBonus = function(){ //取得奖金数额
 return this.strategy.calculate( this.salary ); //把计算奖金的操作委托给对应的策略对象
};

在完成最终的代码之前,我们再来回顾一下策略模式的思想:

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

这句话如果说得更详细一点,就是:定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。

“并且使它们可以相互替换”,这句话在很大程度上是相对于静态类型语言而言的。因为静态类型语言中有类型检查机制,所以各个策略类需要实现同样的接口。当它们的真正类型被隐藏在接口后面时,它们才能被相互替换。而在JavaScript这种“类型模糊”的语言中没有这种困扰,任何对象都可以被替换使用。因此,JavaScript中的“可以相互替换使用”表现为它们具有相同的目标和意图。

现在我们来完成这个例子中剩下的代码。先创建一个bonus对象,并且给bonus对象设置一些原始的数据,比如员工的原始工资数额。接下来把某个计算奖金的策略对象也传入bonus对象内部保存起来。当调用bonus.getBonus()来计算奖金的时候,bonus对象本身并没有能力进行计算,而是把请求委托给了之前保存好的策略对象:

var bonus = new Bonus();

bonus.setSalary( 10000 );
bonus.setStrategy( new performanceS() ); //设置策略对象

console.log( bonus.getBonus() ); // 输出:40000 

bonus.setStrategy( new performanceA() ); //设置策略对象
console.log( bonus.getBonus() ); // 输出:30000

刚刚我们用策略模式重构了这段计算年终奖的代码,可以看到通过策略模式重构之后,代码变得更加清晰,各个类的职责更加鲜明。但这段代码是基于传统面向对象语言的模仿,下一节我们将了解用JavaScript实现的策略模式。

在5.1节中,我们让strategy对象从各个策略类中创建而来,这是模拟一些传统面向对象语言的实现。实际上在JavaScript语言中,函数也是对象,所以更简单和直接的做法是把strategy直接定义为函数:

var strategies = {
 "S": function( salary ){
 return salary * 4;
 },
 "A": function( salary ){
 return salary * 3;
 },
 "B": function( salary ){
 return salary * 2;
 }
};

同样,Context也没有必要必须用Bonus类来表示,我们依然用calculateBonus 函数充当Context来接受用户的请求。经过改造,代码的结构变得更加简洁:

var strategies = {
 "S": function( salary ){
 return salary * 4;
 },
 "A": function( salary ){
 return salary * 3;
 },
 "B": function( salary ){
 return salary * 2;
 }
};

var calculateBonus = function( level, salary ){
 return strategies[ level ]( salary );
};

console.log( calculateBonus( 'S', 20000 ) ); // 输出: 80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出: 30000

3、实例再讲解

一个小例子就能让我们一目了然。
回忆下jquery里的animate方法.

$( div ).animate( {"left: 200px"}, 1000, 'linear' ); 
//匀速运动
$( div ).animate( {"left: 200px"}, 1000, 'cubic' ); 
//三次方的缓动

这2句代码都是让div在1000ms内往右移动200个像素. linear(匀速)和cubic(三次方缓动)就是一种策略模式的封装.

再来一个例子. 很多页面都会有个即时验证的表单. 表单的每个成员都会有一些不同的验证规则.

学习JavaScript设计模式(策略模式)

比如姓名框里面, 需要验证非空,敏感词,字符过长这几种情况。 当然是可以写3个if else来解决,不过这样写代码的扩展性和维护性可想而知。如果表单里面的元素多一点,需要校验的情况多一点,加起来写上百个if else也不是没有可能。

所以更好的做法是把每种验证规则都用策略模式单独的封装起来。需要哪种验证的时候只需要提供这个策略的名字。就像这样:

nameInput.addValidata({
 notNull: true,
 dirtyWords: true,
 maxLength: 30
})
而notNull,maxLength等方法只需要统一的返回true或者false,来表示是否通过了验证。

validataList = {
 notNull: function( value ){
 return value !== '';
 },
 maxLength: function( value, maxLen ){
 return value.length() > maxLen;
 }
}

可以看到,各种验证规则很容易被修改和相互替换。如果某天产品经理建议字符过长的限制改成60个字符。那只需要0.5秒完成这次工作。

大概内容就为大家介绍到这。

聊一聊题外话,马上2015年要过去了,大家的年终奖是不是很丰厚呀!!!

希望大家可以在这一年里有所收获,通过这篇文章也能有所收获,知道什么是策略模式,理解小编精心为大家准备的两个实例。

Javascript 相关文章推荐
自动完成JS类(纯JS, Ajax模式)
Mar 12 Javascript
jQuery实现的Email中的收件人效果(按del键删除)
Mar 20 Javascript
jQuery插件原来如此简单 jQuery插件的机制及实战
Feb 07 Javascript
jQuery列表拖动排列具体实现
Nov 04 Javascript
使用jQuery实现验证上传图片的格式与大小
Dec 03 Javascript
Jquery判断radio、selelct、checkbox是否选中及获取选中值方法总结
Apr 15 Javascript
JS实现带有抽屉效果的产品类网站多级导航菜单代码
Sep 15 Javascript
vue实现todolist单页面应用
Apr 11 Javascript
vue实现一个移动端屏蔽滑动的遮罩层实例
Jun 08 Javascript
jquery版轮播图效果和extend扩展
Jul 18 jQuery
jQuery实现数字华容道小游戏(实例代码)
Jan 16 jQuery
Vue中通过vue-router实现命名视图的问题
Apr 23 Javascript
深入浅析同源策略和跨域访问
Nov 26 #Javascript
js实现iframe框架取值的方法(兼容IE,firefox,chrome等)
Nov 26 #Javascript
学习JavaScript设计模式(单例模式)
Nov 26 #Javascript
javascript bom是什么及bom和dom的区别
Nov 26 #Javascript
Javascript模仿淘宝信用评价实例(附源码)
Nov 26 #Javascript
Javascript BOM学习小结(六)
Nov 26 #Javascript
js实现延时加载Flash的方法
Nov 26 #Javascript
You might like
php简单实现批量上传图片的方法
2016/05/09 PHP
phpcms的分类名称和类别名称的调用
2017/01/05 PHP
PHP设计模式之PHP迭代器模式讲解
2019/03/22 PHP
Laravel 对某一列进行筛选然后求和sum()的例子
2019/10/10 PHP
jQuery写fadeTo示例代码
2014/02/21 Javascript
JavaScript实现的伸展收缩型菜单代码
2015/10/14 Javascript
javascript电商网站抢购倒计时效果实现
2015/11/19 Javascript
JavaScript html5 canvas画布中删除一个块区域的方法
2016/01/26 Javascript
vuejs父子组件通信的问题
2017/01/11 Javascript
Angular实现表单验证功能
2017/11/13 Javascript
js+canvas实现刮刮奖功能
2020/09/13 Javascript
[55:56]NB vs Infamous 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.22
2019/09/05 DOTA
Python入门篇之对象类型
2014/10/17 Python
python在windows命令行下输出彩色文字的方法
2015/03/19 Python
Python的Django框架中模板碎片缓存简介
2015/07/24 Python
Python简单网络编程示例【客户端与服务端】
2017/05/26 Python
JupyterNotebook设置Python环境的方法步骤
2019/12/03 Python
Python手绘可视化工具cutecharts使用实例
2019/12/05 Python
Python-for循环的内部机制
2020/06/12 Python
python“静态”变量、实例变量与本地变量的声明示例
2020/11/13 Python
Python 随机按键模拟2小时
2020/12/30 Python
Python结合百度语音识别实现实时翻译软件的实现
2021/01/18 Python
python中numpy.empty()函数实例讲解
2021/02/05 Python
详解HTML5中download属性的应用
2015/08/06 HTML / CSS
美国单身专业人士在线约会网站:EliteSingles
2019/03/19 全球购物
2014信息技术专业毕业生自我评价
2014/01/17 职场文书
自我评价的范文
2014/02/02 职场文书
大学毕业感言200字
2014/03/09 职场文书
2015年售后服务工作总结
2015/04/25 职场文书
工会文体活动总结
2015/05/07 职场文书
小学德育工作总结2015
2015/05/12 职场文书
大学同学聚会感言
2015/07/30 职场文书
《从现在开始》教学反思
2016/02/16 职场文书
2016年庆祝六一儿童节活动总结
2016/04/06 职场文书
如何写好一份优秀的工作总结?
2019/06/21 职场文书
七年级作文之秋游
2019/10/21 职场文书