详情个人博客: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. Yii2.0怎么设置时区?

    在config文件夹下,main.php 中, return [ 'charset' => 'utf-8', 'language' => 'zh-CN', 'timeZone' => ...

  2. Java诊断利器Arthas

    1 简介 Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar 包加载的?为什么会报各种类相关的 ...

  3. [Note] Clipboard.js 使用

    clipboard.js是一个用来设置剪切板的库,小巧无依赖,但用法有点诡异,必须依赖一个DOM元素 据作者说,由于浏览器相关安全策略的缘故,无法使用下面这种方式来设置剪切板 clipboard.co ...

  4. vue项目引入bootstrap正确姿势

    vue如何引入bootstrap 最近在玩全栈,自然少不了vue的使用.使用vue-cli生成的项目想引入bootstrap,需要先安装相应的npm包,然后在代码中显示引入. 1.安装依赖包: cnp ...

  5. 注册中心nacos完整部署及与eureka区别

    1. 场景描述 nacos最近用的比较多,介绍下nacos及部署吧,刚看了下以前写过类似的,不过没写如何部署及与eureka区别,只展示了效果,补补吧. 2.解决方案 2.1 nacos与eureka ...

  6. SDN网络IPv6组播机制支持实时视频业务海量用户扩展

    以 OpenFlow 技术为核心的软件定义网络(SDN)框架具有集中控制的功能能够自己感知网络拓扑的变化,在细粒度的路径选择.接入控制.负载均衡方面有着天然的优势,为 IPv6 组播功能的实现提供了好 ...

  7. Oracle 编写存储过程并设置定时执行

    Oracle 版本:11g PL/SQL 版本:11.0.3.1770 1.如果编写单个存储过程,则直接在procedure新建并编写:如果需要写多个同类功能的存储过程,为了方便管理,可以现在pack ...

  8. 除法分块 luogu2261 (坑)

    除法分块 除法分块 是指使用分块计算的方法求S=∑i=1n⌊ki⌋S=\sum^{n}_{i=1}{\lfloor{\frac{k}{i}}\rfloor}S=i=1∑n​⌊ik​⌋的值. 举个例子. ...

  9. 利用Veeam保护SAP HANA数据库

    利用Veeam保护SAP HANA数据库 前言 针对越来越多的SAP HANA备份需求,我们Team翻译.整理.借鉴了Veeam 的SAP HANA 大神 Clemens Zerbe 和 Ali Sa ...

  10. redis应用场景总结redis平时我们用到的地方蛮多的,下面就了解的应用场景做个总结:

    redis常见应用场景 1.热点数据的缓存 由于redis访问速度块.支持的数据类型比较丰富,所以redis很适合用来存储热点数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作,这 ...