设计模式高大上,业务代码用不上...平时用不上我们就可以忽略了吗?

非也,就像面试造火箭,工作拧螺丝一样。万一我们公司哪天要造火箭了,你得立马能上手。

同时,有些复杂的业务代码也可以用设计模式的思想去写,如果你对设计模式不了解,到时怎么会想到呢?那你写出来的代码怕是很难去阅读和维护。

说了那么多,就是想说一点:设计模式很棒!你可以不用,但不能不会!下面来聊聊策略模式:

策略模式的定义

何为策略?比如我们要去西藏旅游,可以根据自身的实际情况来选择出行的路线。

如果没有时间但是不在乎钱,可以选择坐飞机。

​ 如果没有钱,时间还算充足,可以选择坐火车。

​ 如果再穷一点,时间充足,可以选择骑行。

​

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

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

废话不多说,下面直接show code...

年终奖实例

年底了,又到了发年终奖的时候。有些公司的年终奖根据员工的工资基数和绩效等级来发放的。例如,绩效为A的人年终奖有4倍工资,绩效为B的有3倍,绩效为C的只有2倍。

假如财务部要求我们提供一段代码来实现这个核算逻辑,我们要怎么实现呢?

初级-一个函数搞定

这不是很简单嘛,写一个函数,传入工资基数和绩效等级,返回最终计算的奖金。代码如下:

var calculateBonus = function( performanceLevel, salary ){
​ if ( performanceLevel === 'A' ){
​ return salary * 4;
​ } ​ if ( performanceLevel === 'B' ){
​ return salary * 3;
​ } ​ if ( performanceLevel === 'C' ){
​ return salary * 2;
​ }
}; calculateBonus( 'B', 20000 ); // 输出:40000 calculateBonus( 'S', 6000 ); // 输出:24000

可以发现,这段代码十分简单,但是存在着明显的缺点:

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

​ calculateBonus函数缺乏弹性,如果增加了一种新的绩效等级D,或者把A等级的倍数改成5,那我们必须深入。

​ calculateBonus函数的内部实现,这是违反开放-封闭原则的。

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

进阶-组合函数重构代码

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

var performanceA = function( salary ){
​ return salary * 4;
}; var performanceB = function( salary ){
​ return salary * 3;
}; var performanceC = function( salary ){
​ return salary * 2;
}; var calculateBonus = function( performanceLevel, salary ){
​ if ( performanceLevel === 'A' ){
​ return performanceA( salary );
​ } ​ if ( performanceLevel === 'B' ){
​ return performanceB( salary );
​ } ​ if ( performanceLevel === 'C' ){
​ return performanceC( salary );
​ }
}; calculateBonus( 'A' , 10000 ); // 输出:40000

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

终极-策略模式解决痛点

策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外。策略模式的目的就是将算法的使用与算法的实现分离。

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

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

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

var performanceA = function() {};
performanceA.prototype.calculate = function (salary) {
​ return salary * 4
} var performanceB = function(){};
performanceB.prototype.calculate = function( salary ){
​ return salary * 3;
}; var performanceC = function(){};
performanceC.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中的“可以相互替换使用”变现为它们具有相同的目标和意图。

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

var bonus = new Bonus();
bonus.setSalary( 10000 ); bonus.setStrategy( new performanceA() ); //设置策略对象
console.log( bonus.getBonus() ); // 输出:40000 bonus.setStrategy( new performanceB() ); //设置策略对象
console.log( bonus.getBonus() ); // 输出:30000

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

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

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

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

var strategies = {
​ "A": function( salary ){
​ return salary * 4;
​ }, ​ "B": function( salary ){
​ return salary * 3;
​ }, ​ "C": function( salary ){
​ return salary * 2;
​ }
}; var calculateBonus = function( level, salary ){
​ return strategies[level](salary)
}; console.log( calculateBonus( 'A', 20000 ) ); // 输出: 80000
console.log( calculateBonus( 'B', 10000 ) ); // 输出: 30000

上面的例子让我们对策略模式有了初步了解,其实总结起来就干了一件事:解耦。下面我们再来看看策略模式在jQuery中的应用:

$( div ).animate( {"left: 200px"}, 1000, 'linear' );  //匀速运动

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

相信大家看到这两段代码就很熟悉了吧。

优缺点

  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
  • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
  • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
  • 在策略模式中利用组合和委托来让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

当然,策略模式也有一些缺点,但这些缺点并不严重。

首先,使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在Context中要好。 其次,要使用策略模式,必须了解所有的strategy(策略的名字要通过API暴露),必须了解各个strategy之间的不同点,这样才能选择一个合适的strategy。比如,我们要选择一种合适的旅游出行路线,必须先了解选 择飞机、火车、自行车等方案的细节。此时strategy要向客户暴露它的所有实现,这是违反最少知识原则的。

参考:精读JavaScript设计模式之策略模式

JS设计模式——策略模式的更多相关文章

  1. [转]js设计模式-策略模式

    在程序设计中,常常遇到类似的情况,要实现某一个功能有多种方案可以选择.比如一个压缩文件的程序,既可以选择zip算法,也可以选择gzip算法.这些算法灵活多样,而且可以随意互相替换.这种解决方案就是本文 ...

  2. js设计模式--策略模式

    策略模式: 定义了一系列的算法,把他们封装起来,是它们之间可以互相替换,此模式不会影响到使用算法的客户. 回忆下jquery里的animate方法: $( div ).animate( {" ...

  3. js 设计模式——策略模式

    策略模式(Strategy) 定义:将定义的一组算法封装起来,使其相互之间可以替换.封装的算法具有一定的独立性,不会随客户端的变化而变化 废话不多说,先来个例子 // 例如要写一个计算两个数加减乘除的 ...

  4. 15. 星际争霸之php设计模式--策略模式

    题记==============================================================================本php设计模式专辑来源于博客(jymo ...

  5. [.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型)

    [.net 面向对象程序设计深入](24)实战设计模式——策略模式(行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它 ...

  6. linkin大话设计模式--策略模式

    linkin大话设计模式--策略模式 Strategy [ˈstrætədʒi]  策略 策略模式用于封装系列的算法,这些算法通常被封装在一个称为Context的类中,客户端程序可以自由的选择任何一种 ...

  7. [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型)

    [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模 ...

  8. JAVA 设计模式 策略模式

    用途 Title 它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户. 策略模式是一种行为型模式. 结构

  9. 设计模式-策略模式(Strategy Model)

    1.概述     在开发过程中常常会遇到类似问题,实现一个功能的时候往往有多种算法/方法(策略),我们可以根据环境的不同来使用不同的算法或策略来实现这一功能.     如在人物比较排序的实现中,我们有 ...

随机推荐

  1. hdu 5139 数据的离线处理

    所谓的数据离线处理,就是将所有的输入数据全部读入后,在进行统一的操作,这样当然有好处,比如让你算好多数的阶层,但是输入的每个数是没有顺序的,其实跟可以线性的解决,但是由于没有顺序的输入,这样处理的话复 ...

  2. cf - 920 c 求能否实现交换

    C. Swap Adjacent Elements time limit per test 1 second memory limit per test 256 megabytes input sta ...

  3. Django后台管理系统的使用

    目录 django后台管理系统的使用 检查配置文件 检查根urls.py文件 启动项目,浏览器输入ip端口/admin 如: 127.0.0.1/8000/admin 回车 注册后台管理系统超级管理 ...

  4. Python第一个请求接口

    1.普通get请求 import requests import json login_res=requests.post(url='http://joy.web.com:8090/login',da ...

  5. Postman post csrf_token

    1.填入代码 var csrf_token = postman.getResponseCookie("csrftoken").value postman.clearGlobalVa ...

  6. 为什么你应该使用 Kubernetes(k8s)

    Kubernetes (Kube 或 K8s)越来越流行,他是市场上最好的容器编排工具之一. 1. 什么是容器? 容器就是一个包,其中包含了应用及其所有依赖. 容器中的应用与主机系统是隔离的,不关注环 ...

  7. C++ vector对象是如何增长的

    为了支持快速随机访问,vector将元素连续存储---每个元素紧挨着前一个元素存储. 如果没有空间容纳新元素: 容器必须分配新的内存空间来保存已有元素和新元素,将已有元素从旧位置移动到新空间中,然后添 ...

  8. .net core mysql CodeFirst

    创建两个项目 1.网站 2.Model层 引用DLL Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.Design Micros ...

  9. StampedLock的理解和使用

    StampedLock介绍 StampedLock是为了优化可重入读写锁性能的一个锁实现工具,jdk8开始引入 相比于普通的ReentranReadWriteLock主要多了一种乐观读的功能 在API ...

  10. Dynamics CRM Tips

    这里是作为开发中遇到的各式各样的问题的总结贴. 如果对Dynamics CRM 开发有兴趣, 请参考Step by Step 开发dynamics CRM 移除sitemap中的entity 从O36 ...