详情个人博客: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. Spring GetBean流程

     第一节讲解Spring启动的时候说到,Spring内部先解析了所有的配置,加载所有的Bean定义后,再根据需要对Bean进行实例化和初始化.除开Spring自己主动新建的对象,第一次根据Bean定义 ...

  2. .Net Core 商城微服务项目系列(十一):MQ消费端独立为Window服务+消息处理服务

    之前使用MQ的时候是通过封装成dll发布Nuget包来使用,消息的发布和消费都耦合在使用的站点和服务里,这样会造成两个问题: 1.增加服务和站点的压力,因为每次消息的消费就意味着接口的调用,这部分的压 ...

  3. Python 正则re匹配中文、英式数字

    #coding:utf-8 import re s = u''' 或多或少的好好读书电锯惊魂20202 和水电费后是否会时候1212没收到风10.12海大富的是粉红色的和办法的1244525.000 ...

  4. Spring Boot 2.x 基础案例:整合Dubbo 2.7.3+Nacos1.1.3(最新版)

    1.概述 本文将介绍如何基于Spring Boot 2.x的版本,通过Nacos作为配置与注册中心,实现Dubbo服务的注册与消费. 整合组件的版本说明: Spring Boot 2.1.9 Dubb ...

  5. springmvc框架的使用

    1.在idea中新建java web项目,引入spring和springmvc所需jar包,其中 spring-web-3.2.12.RELEASE.jar:在web应用开发时使用spring框架所需 ...

  6. rest_framework视图

    知识预览 视图 回到顶部 视图 使用混合(mixins) 上一节的视图部分:   from rest_framework.views import APIView from rest_framewor ...

  7. 在vue中操作dom元素

    在vue中可以通过给标签加ref属性,就可以在js中利用ref去引用它,从而达到操作该dom元素,以下是个参考例子 1 <template> 2 <div> 3 <div ...

  8. 项目开发---使用node.js中sass语法

    前言:本文中所有sass文件都指后缀名为scss的文件.在此也建议使用后缀名为scss的文件,以避免sass后缀名的严格格式要求报错. 一.sass插件的安装: gulp-sass-china //  ...

  9. 数据结构5_java---二叉树,树的建立,树的先序、中序、后序遍历(递归和非递归算法),层次遍历(广度优先遍历),深度优先遍历,树的深度(递归算法)

    1.二叉树的建立 首先,定义数组存储树的data,然后使用list集合将所有的二叉树结点都包含进去,最后给每个父亲结点赋予左右孩子. 需要注意的是:最后一个父亲结点需要单独处理 public stat ...

  10. 使用git如何规范地向主线提交代码

    使用git向主干分支合并代码通常采用两种方式:第一种是merge,第二种是利用BeyondCompare等工具进行比对,将差异合并到主干: 通过merge合并代码出现冲突时,并不清楚谁的修改和谁的修改 ...