俗话说,条条大路通罗马。在美剧《越狱》中,主角Michael Scofield就设计了两条越狱的道路。这两条道路都可以到达靠近监狱外墙的医务室。

同样,在现实中,很多时候也有多种途径到达同一个目的地。比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路。

  • 如果没有时间但是不在乎钱,可以选择坐飞机。
  • 如果没有钱,可以选择坐大巴或者火车。
  • 如果再穷一点,可以选择骑自行车。


在程序设计中,我们也常常遇到类似的情况,要实现某一个功能有多种方案可以选择。比如一个压缩文件的程序,既可以选择zip算法,也可以选择gzip算法。

这些算法灵活多样,而且可以随意互相替换。这种解决方案就是本章将要介绍的策略模式。

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

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

很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为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 );    //把计算奖金的操作委托给对应的策略对象
};

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

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

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

1. “并且使它们可以相互替换”,这句话在很大程度上是相对于静态类型语言而言的。因为静态类型语言中有类型检查机制,所以各个策略类需要实现同样的接口。当它们的真正类型被隐藏在接口后面时,它们才能被相互替换。而在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

在接下来的缓动动画和表单验证的例子中,我们用到的都是这种函数形式的策略对象。

本文摘自《JavaScript设计模式与开发实践》

JavaScript版本的策略模式相关推荐

  1. JavaScript设计模式之策略模式(学习笔记)

    在网上搜索"为什么MVC不是一种设计模式呢?"其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composi ...

  2. javascript篇:策略模式验证表单

    策略模式在运行时选择算法,其中一个例子就是用来解决表单验证的问题,通过创建一个validate()方法的验证器(validator)对象. 无论表单的具体类型是什么,该方法都将会被调用,并且总是返回相 ...

  3. python版本的策略模式

    策略提供一种用多个行为中的一个行为来配置一个类的方法.其实就是应用于同一个问题的不同解决方案 # -*- coding:UTF-8 -*- import abcclass Strategy(metac ...

  4. [设计模式] javascript 之 策略模式

    策略模式说明 定义: 封装一系列的算法,使得他们之间可以相互替换,本模式使用算法独立于使用它的客户的变化. 说明:策略模式,是一种组织算法的模式,核心不在于算法,而在于组织一系列的算法,并且如何去使用 ...

  5. JS设计模式(2)策略模式

    什么是策略模式? 定义:根据不同参数可以命中不同的策略 主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护. 何时使用:有许多种情况,而区分它们的只是他们直接的行为. ...

  6. JavaScript 设计模式核⼼原理与应⽤实践 之 行为型:策略模式——重构小能手,拆分“胖逻辑”

    JavaScript 设计模式核⼼原理与应⽤实践 之 行为型:策略模式--重构小能手,拆分"胖逻辑" 行为型:策略模式--重构小能手,拆分"胖逻辑" 策略模式和 ...

  7. 深入理解JavaScript系列(33):设计模式之策略模式(转)

    介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...

  8. 深入理解JavaScript系列(33):设计模式之策略模式

    介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很 ...

  9. 鸿蒙笔记本双系统,浅谈鸿蒙OS发展模式 华为P50系列推行双系统版本+双芯片策略...

    据透露,华为将会在今年上半年如期发布新一代旗舰手机-华为P50系列,而华为P50系列手机将会搭载Android.鸿蒙双系统版本,也就是说华为P50系列手机,将成为全球首款预装鸿蒙OS系统机型. 据介绍 ...

最新文章

  1. Java基础(二) 程序格式
  2. 内核中的HZ 及延迟等
  3. python怎么变成竖行_python 处理文本 列变成行
  4. Atitit. 包厢记时系统 的说明,教程,维护,故障排查手册v2 pb25.doc
  5. 前端开发~uni-app ·[项目-仿糗事百科] 学习笔记 ·000【前言】
  6. BZOJ3261: 最大异或和(可持久化trie树)
  7. TYVJ P1022 进制转换 Label:坑
  8. GlobalMapper20使用控制点对地形数据(高程数据)进行高程纠正(高程拟合/纠偏/配准)
  9. STM32连接摄像头知识积累
  10. c语言开发 kdj,KDJ——随机指标之王
  11. 中国身份证号码验证,支持15,18位
  12. MySQL高级索引及调优篇
  13. Unity 2D Animation(2D动画)学习
  14. 专用计算机的运行速度,WIN10专业版下电脑运行速度慢多个解决技巧
  15. 实战:使用urllib.request爬取猫眼票房数据
  16. 刷APP任务平台可靠吗?
  17. jquery 遍历java对象_jquery遍历数组、对象
  18. Linux perf: 为什么采样频率设置为99Hz而不是100Hz?
  19. class二进制文件解析(一)
  20. 硬盘修复真经 误区、缺陷、参数与低格

热门文章

  1. grub2引导linux内核,一种基于grub2的linux系统启动bootloader的制作方法与流程
  2. html5改成块状标签,HTML5基本网页结构以及标签的改变
  3. 邻接矩阵中啥时候写0和无穷_集合中的上极限与下极限
  4. 【c语言】蓝桥杯算法提高 输出三个整数的最大数
  5. 「小程序JAVA实战」微信开发者工具helloworld(三)
  6. LDialog基于DialogFragment封装的库,也许是一个Nice的库
  7. JAVA-JSP内置对象
  8. Linux-Ubuntu操作记录
  9. windows 80端口被占用的解决方法
  10. OCP读书笔记(5) - 使用RMAN创建备份