详情个人博客:https://shengchangwei.github.io/js-shejimoshi-celue/

策略模式

1、定义

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

2、使用策略模式计算奖金

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

1. 最初的实现代码 --(缺点太多)

编写calculateBonus函数接收两个参数: 员工的工资数额和他的绩效考核等级。

代码如下:

 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;
};
2
var performanceA = function( salary ){
return salary * 3;
};
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 8

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

3. 使用策略模式重构代码(模仿传统面向对象语言中的实现)(作为了解)

// 绩效S
var performanceS = function(){};
performanceS.prototype.calculate = function(salary) {
return salary * 4;
}
// 绩效A
var performanceA = function(){};
performanceA.prototype.calculate = function(salary) {
return salary * 3;
}
// 绩效B
var performanceA = function(){};
performanceA.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); // 把计算奖金的操作委托给对应的策略对象
} // 使用
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

这段代码是基于传统面向对象语言模仿,自我感觉太过啰嗦,一些比较少的判断太过啰嗦

3、JavaScript 版本的策略模式 (平时自己写代码用的比较多的思想)

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

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

在自己平时项目开发中,遇到许多if-else判断时,首先考虑到将这些不变的条件独立封装成一个对象,然后根据判断条件从对象中获取对应的值,在这里多说几句,其实个人感觉我们在实际开发中或多或少的都用到一些设计模式,只是自己没有将一些思想提取出来,没有归类总结。而学习设计模式正是将我们实际开发中自己用到的一些思想归类,将那种模糊想要抓住的思想用一个名称去标识,下次遇到同样的业务问题可以很快的定位到自己要使用的模式,这是一种提升,废话少说,继续下文。

4、策略模式 -- 表单验证

规则:

  • 用户名不能为空
  • 密码长度不能少于6位
  • 手机密码必须符合格式

4.1 第一版本(最基础的)

html

<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName"/ >
请输入密码: <input type="text" name="password"/ >
请输入手机号码:<input type="text" name="phoneNumber"/ >
<button>提交</button>
</form>

js

var registerForm = document.getElementById( 'registerForm' );
registerForm.onsubmit = function(){
if ( registerForm.userName.value === '' ){
alert ( '用户名不能为空')
return false;
}
if ( registerForm.password.value.length < 6 ){
alert ( '密码长度不能少于 6 位' );
return false;
}
if ( !/(^1[3|5|8][0-9]{9}$)/.test( registerForm.phoneNumber.value ) ){
alert ( '手机号码格式不正确' );
return false; }
}

缺点:

  • registerForm.onsubmit 函数比较庞大,包含了很多 if-else 语句,这些语句需要覆盖所有的校验规则。
  • registerForm.onsubmit 函数缺乏弹性,如果增加了一种新的校验规则,或者想把密码的长度校验从 6 改成 8,我们都必须深入 registerForm.onsubmit 函数的内部实现,这是违反开放—封闭原则的。
  • 算法的复用性差,如果在程序中增加了另外一个表单,这个表单也需要进行一些类似的校验,那我们很可能将这些校验逻辑复制得漫天遍野。

4.2 策略模式重构表单校验

代码如下:

var strategies = {
isNonEmpty: function(value, errorMsg) { // 不能为空
if(value === '') {
return errorMsg;
}
},
minLength: function(value, length, errorMsg) { // 限制长度
if(value.length < length) {
return errorMsg;
}
},
isMobile: function(value, errorMsg) {
if( !/(^1[3|5|8][0-9]{9}$)/.test( value )) {
return errorMsg;l
}
}
}

接下来我们准备实现 Validator 类。Validator 类在这里作为 Context,负责接收用户的请求并委托给 strategy 对象。在给出 Validator 类的代码之前,有必要提前了解用户是如何向 Validator类发送请求的,这有助于我们知道如何去编写 Validator 类的代码。代码如下:

var validataFunc = function() {
var validator = new Validator(); // 创建一个对象 Validtor类在下面
/*****************添加一些校验规则*******************/
validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空');
validator.add(registerForm.password, 'minLength:6', '密码长度不能少于6位 ');
validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');
var errorMsg = validator.start(); // 获得校验结果
return errorMsg; // 返回校验结果
}
var registerForm = document.getElementById( 'registerForm' );
registerForm.onsubmit = function() {
var errorMsg = validataFunc();
if(errorMsg) {
alert(errorMsg);
return false;
}
}

最后是Validator类的实现

var Validator = function() {
this.cache = [];
};
Validator.prototype.add = function(dom, rule, errorMsg) {
var ary = rule.split(':');
this.cache.push(function() {
var strategy = ary.shift();
ary.unshift(dom.value);
ary.push(errorMsg);
return strategies[strategy].apply(dom, ary);
})
};
Validator.prototype.start = function() {
for(var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ];) {
var msg = validatorFunc();
if ( msg ){ // 如果有确切的返回值,说明校验没有通过
return msg;
}
}
}

4.3 给某个文本输入框添加多种校验规则

期待校验方式:

validator.add( registerForm.userName,
[{
errorMsg: '用户名不能为空'
strategy: 'isNonEmpty',
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于 10 位'
}]
);

下面提供的代码可用于一个文本输入框对应多种校验规则:

<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName"/ >
请输入密码:<input type="text" name="password"/ >
请输入手机号码:<input type="text" name="phoneNumber"/ >
<button>提交</button>
</form>
/***********************策略对象**************************/
var strategies = {
isNonEmpty: function( value, errorMsg ){
if ( value === '' ){
return errorMsg;
}
},
minLength: function( value, length, errorMsg ){
if ( value.length < length ){
return errorMsg;
}
},
isMobile: function( value, errorMsg ){
if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
return errorMsg;
}
}
};
/***********************Validator 类**************************/
var Validator = function(){
this.cache = [];
};
Validator.prototype.add = function( dom, rules ){
var self = this;
for ( var i = 0, rule; rule = rules[ i++ ]; ){
(function( rule ){
var strategyAry = rule.strategy.split( ':' );
var errorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift( dom.value );
strategyAry.push( errorMsg );
return strategies[ strategy ].apply( dom, strategyAry );
});
})( rule )
}
};
Validator.prototype.start = function(){
for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
var errorMsg = validatorFunc();
if ( errorMsg ){
return errorMsg;
}
}
};
/***********************客户调用代码**************************/
var registerForm = document.getElementById( 'registerForm' );
var validataFunc = function(){
var validator = new Validator();
validator.add( registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于 10 位'
}]);
validator.add( registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码长度不能小于 6 位'
}]);
validator.add( registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function(){
var errorMsg = validataFunc();
if ( errorMsg ){
alert ( errorMsg );
return false;
}
};

5、策略模式的优缺点

优点:

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

缺点:(了解)

  • 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在 Context 中要好。
  • 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点,这样才能选择一个合适的 strategy。违反最少知识原则的。

6、小结

在平时项目开发中,遇到许多if-else判断时,首先考虑策略模式的思想,将一些不变的东西单独封装成对象或函数。

《JavaScript设计模式与开发实践》-- 策略模式的更多相关文章

  1. 《Android源码设计模式》学习笔记之ImageLoader

    微信公众号:CodingAndroid cnblog:http://www.cnblogs.com/angel88/ CSDN:http://blog.csdn.net/xinpengfei521 需 ...

  2. 《Android源码设计模式》--抽象工厂模式

    No1: 4种MediaPlayer Factory分别会生成不同的MediaPlayer基类:StagefrightPlayer.NuPlayerDriver.MidiFile和TestPlayer ...

  3. 《Android源码设计模式》--Builder模式

    No1: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 No2: 在Android源码中,最常用到的Builder模式就是AlertDialog.Builder No3: ...

  4. 《Android源码设计模式》--策略模式

    No1: 定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化. No2: 使用场景: 1)针对同一类型问题的多种处理方式,仅 ...

  5. 《Android源码设计模式》--模板方法模式

    No1: 模板方法模式包括:抽象类(其中定义了一系列顺序方法).具体实现类A.具体实现类B 如果子类有实现不一样的细节,重写父类的某个方法即可 No2: AsyncTask对象调用execute方法后 ...

  6. 《Android源码设计模式》--状态模式--责任链模式--解释器模式--命令模式--观察者模式--备忘录模式--迭代器模式

    [状态模式] No1: Wifi设置界面是一个叫做WifiSetting的Fragment实现的 No2: 在不同的状态下对于扫描Wifi这个请求的处理是完全不一样的.在初始状态下扫描请求被直接忽略, ...

  7. 《Android源码设计模式》--享元模式

    No1: 享元模式是对象池的一种实现.享元模式用来尽可能减少内存使用量,它适合用于可能存在大量重复对象的场景,来缓存可共享的对象,达到对象共享.避免创建过多对象的效果,这样一来就可以提升性能.避免内存 ...

  8. 《Android源码设计模式》--工厂方法模式

    No1: 对于一个应用程序来说,其真正的入口是在ActivityThread类中,ActivityThread中含有我们熟悉的main方法.ActivityThread是一个final类,不能被继承. ...

  9. 《Android源码设计模式》--原型模式

    No1: 原型模式使用场景: 1)类初始化需要消耗非常多的资源,这个资源包括数据.硬件资源等,通过原型复制避免这些消耗 2)通过new产生一个对象需要非常繁琐的数据准备货访问权限,这是可以使用原型模式 ...

  10. 《Android源码设计模式》--装饰模式

    No1: Activity继承于ContextThemeWrapper,继承于ContextWrapper,继承于Context. No2: Context中方法的所有实现均由ContextImpl类 ...

随机推荐

  1. css 实现图片灰度

    先看效果鼠标移入图片中摁下向左移动 图片由灰度变为原图   向右移动原图变灰度 ​ 代码如下:尚未做优化 <style> *{ margin:0; padding:0; } #img{ w ...

  2. Scala 学习笔记之函数(1)

    class Student { val mySayHello1: String => Unit = sayHello val mySayHello2: (String, Int) => U ...

  3. spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)

    面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理. 事务的介绍 1.数据库事物特性 原子性多个数据库操作是不可分割的,只有所有的操作 ...

  4. Android 调用 WebService

    1.WebService简介 PS:如果看完上面简介还不是很清楚的话,那么就算了,之前公司就用C#搭的一个WebService! 本节我们并不讨论如何去搭建一个WebService,我们仅仅知道如何去 ...

  5. kali切换到西电源

    准备研究kali的openvas,打开发现居然没有.apt-get更新一下结果各种报错,换成中科大源.阿里源还是始终报错,气到吐血.最后上西电开源社区换成了西电的kali源,更新速度2m多,一气呵成~ ...

  6. JSON parse error: No suitable constructor found for type

    错误信息: 2019-02-19 09:17:58,678 [http-nio-8080-exec-1] WARN org.springframework.web.servlet.mvc.suppor ...

  7. OEMCC 13.3 主机agent部署问题排查

    部署安装 具体的安装过程可参考,Alfred Zhao的文章,非常详细,文章是OEMCC13.2的部署过程.OEMCC13.3没有太大差别. https://www.cnblogs.com/jyzha ...

  8. 【DP合集】合并 union

    给出一个 1 ∼ N 的序列 A ( A 1 , A 2 , ..., A N ) .你每次可以将两个相邻的元素合并,合并后的元素权值即为 这两个元素的权值之和.求将 A 变为一个非降序列,最少需要多 ...

  9. Ubuntu安装exfat工具

    sudo apt-get undate sudo apt-get install exfat-utils

  10. POJ2828 Buy Tickets 树状数组

    Description Railway tickets were difficult to buy around the Lunar New Year in China, so we must get ...