介绍

策略模式的意义是定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。此模式让算法的变化不会影响到使用算法的客户。

实现

举一个例子,比如我们做数据合法性校验,一般是通过swich来实现,或者通过if语句来实现,如果校验规则多了的话,那么代码的扩展性和维护性就很差了,而且进行单元测试就越来越复杂,代码如下:

var validator = {
validate: function(value,type) {
switch(type) {
case 'isNonEmpty':
return true
case 'isNumber':
return true;
case 'isAlphaNum':
return true;
default:
return true;
}
}
} alert(validator.validate('123','isNonEmpty'))

怎么避免上面代码的弊端呢,我们可以使用策略模式把相同的工作代码封装成不同的验证类,我们只需要通过传递不同的名称来调用不同的验证类方法(也即是不同的算法),实现代码如下:

var validator = {
types: { // 存放验证规则
isNonEmpty: {
validate: function(value) {
return value !== ''
},
instructions: '传入的值不能为空'
},
isNumber: {
validate: function(value) {
return !isNaN(value);
},
instructions: '传入的值不是数字'
},
isAlphaNum: {
validate: function(value) {
return !/[^a-z0-9]/i.test(value)
},
instructions: '传入的值只能是数字或者字母,不能是特殊字符'
}
},
config: {}, // 需要验证的类型
messages: [], // 存放错误信息
validate: function(data) { // 传入的data 为key-value的键值对
var i, type, checker, resultOk;
this.messages = []; // 首先清空错误信息
for(i in data) {
if(data.hasOwnProperty(i)) { // 判断i不是原型上的属性
type = this.config[i]; // 获取验证类型
if(!type) { // 没有当前校验类型直接跳过(不需要验证的)
continue;
}
checker = this.types[type]; // 获取验证规则的验证类方法
if(!checker) { // 验证规则类不存在 直接抛出异常
throw {
name: "ValidationError",
message: "No handler to validate type " + type
}
}
resultOk = checker.validate(data[i]);
if(!resultOk) { // 验证不通过
this.messages.push(checker.instructions);
}
}
}
return this.hasErrors();
},
hasErrors: function() {
return this.messages.length !== 0;
} }

使用方式如下:

var data = {
firstName: '',
lasName: 'shu',
age: '',
userName: 'tom shu'
}
validator.config = {
firstName: 'isNonEmpty',
age: 'isNumber',
userName: 'isAlphaNum'
} validator.validate(data);
if(validator.hasErrors()) {
console.log(validator.messages.join("\n"));
}
// 结果:
// 传入的值不能为空
// 传入的值只能是数字或者字母,不能是特殊字符

其它策略模式示例

jquery中使用的animate方法

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

这 2 句代码都是让 div 在 1000ms 内往右移动 200 个像素. linear(匀速) 和 cubic(三次方缓动) 就是一种策略模式的封装.

计算奖金

比如公司的年终奖是根据员工的工资和绩效来考核的,绩效为A的人,年终奖为工资的4倍,绩效为B的人,年终奖为工资的3倍,绩效为C的人,年终奖为工资的2倍;现在我们使用一般的编码方式会如下这样编写代码:

var calculateBouns = function(salary,level) {
if(level === 'A') {
return salary * 4;
}
if(level === 'B') {
return salary * 3;
}
if(level === 'C') {
return salary * 2;
}
};
// 调用如下:
console.log(calculateBouns(4000,'A')); // 16000
console.log(calculateBouns(2500,'B')); // 7500

缺点:函数含有很多if语句,缺乏弹性,算法复用性差,如果其它地方有类似的算法,但是规则不一样,这些代码不能通用。

使用策略模式代码如下:

//代码如下:
var obj = {
"A": function(salary) {
return salary * 4;
},
"B" : function(salary) {
return salary * 3;
},
"C" : function(salary) {
return salary * 2;
}
};
var calculateBouns =function(level,salary) {
return obj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000

策略模式不仅仅只封装算法,我们还可以对用来封装一系列的业务规则,只要这些业务规则目标一致,我们就可以使用策略模式来封装它们

表单校验

比如常见的就是注册页面,需要对用户名,密码,手机号等进行规则校验,验证规则如下:

  • 用户名不能为空;
  • 密码不能小于6位;
  • 手机号码符合手机正则规则

HTML代码如下:

<form action="" id="registerForm" method="post" onsubmit="return submitValidate()">
<p>
<label>请输入用户名:</label>
<input type="text" name="userName" />
</p>
<p>
<label>请输入密码:</label>
<input type="text" name="password" />
</p>
<p>
<label>请输入手机号码:</label>
<input type="text" name="phoneNumber" />
</p>
<div>
<button type="submit">提交</button>
</div>
</form>

submitValidate验证方法如下:

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

缺点:

  • submitValidate函数中的if else-if代码会根据验证项而逐渐变大;
  • submitValidate函数缺乏弹性,如果添加新的校验规则是添加else-if语句,但是如果是修改原来的验证规则,那么就需要改函数内的代码,违反开放-封闭原则;
  • 算法的复用性差,如果其它页面也需要用类似的校验,那么这个方法就不能共用了,可以又是复制代码。

下面我们使用策略模式来重构上面的代码。

第一步,封装策略对象,也即是验证的不同算法,代码如下:

var strategys = {
isNotEmpty: function(value,errorMsg) {
if(value === '') {
return errorMsg;
}
},
// 限制最小长度
minLength: function(value,length,errorMsg) {
if(value.length < length) {
return errorMsg;
}
},
// 手机号格式
mobileFormat: function(value,errorMsg) {
if(!/(^1[0-9]{10}$)/.test(value)) {
return errorMsg;
}
}
}

第二步,实现Validator类,Validator类在这里作为Context,负责接收用户的请求并委托给strategy 对象。

通俗的话就是添加表单中需要验证的一些规则以及获取验证结果(是否验证通过),如下代码:

function Validator() {
this.cache = []; // 保存效验规则
} Validator.prototype = {
constructor: Validator,
add: function(dom,rule,errorMsg) {
var str = rule.split(":"); // minLength:6的场景
var fn = function() {
var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
str.unshift(dom.value); // 往数组str的第一位插入value值
str.push(errorMsg);
return strategys[strategyType].apply(dom,str);
}
this.cache.push(fn);
},
start: function() {
for(var i = 0, fn; fn = this.cache[i++];) {
var msg = fn();
if(msg) {
return msg;
}
}
}
}

调用方式:

function submitValidate() {
var registerForm = document.getElementById("registerForm");
var validator = new Validator();
validator.add(registerForm.userName,'isNotEmpty', '用户名不能为空');
validator.add(registerForm.password,'minLength:6', '密码的长度不能小于6位');
validator.add(registerForm.phoneNumber,'mobileFormat', '手机号码格式不正确');
var resultMsg = validator.start();
if(resultMsg) {
alert(resultMsg);
return false;
}
return true;
}

完整的JS代码:

var strategys = {
isNotEmpty: function(value,errorMsg) {
if(value === '') {
return errorMsg;
}
},
// 限制最小长度
minLength: function(value,length,errorMsg) {
if(value.length < length) {
return errorMsg;
}
},
// 手机号格式
mobileFormat: function(value,errorMsg) {
if(!/(^1[0-9]{10}$)/.test(value)) {
return errorMsg;
}
}
} function Validator() {
this.cache = []; // 保存效验规则
} Validator.prototype = {
constructor: Validator,
add: function(dom,rule,errorMsg) {
var str = rule.split(":"); // minLength:6的场景
var fn = function() {
var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
str.unshift(dom.value); // 往数组str的第一位插入value值
str.push(errorMsg);
return strategys[strategyType].apply(dom,str);
}
this.cache.push(fn);
},
start: function() {
for(var i = 0, fn; fn = this.cache[i++];) {
var msg = fn();
if(msg) {
return msg;
}
}
}
} function submitValidate() {
var registerForm = document.getElementById("registerForm");
var validator = new Validator();
validator.add(registerForm.userName,'isNotEmpty', '用户名不能为空');
validator.add(registerForm.password,'minLength:6', '密码的长度不能小于6位');
validator.add(registerForm.phoneNumber,'mobileFormat', '手机号码格式不正确');
var resultMsg = validator.start();
if(resultMsg) {
alert(resultMsg);
return false;
}
return true;
}

以上代码我们只实现了给一个dom元素绑定一条验证规则,那如果需要绑定多条验证规则呢?

比如上面的代码我们只能效验输入框是否为空,validator.add(registerForm.userName,'isNotEmpty','用户名不能为空');但是如果我们既要效验输入框是否为空,还要效验输入框的长度不要小于10位的话,那么我们期望需要像如下传递参数:

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

我们只需要修改一下add方法即可,如下代码:

function Validator() {
this.cache = []; // 保存效验规则
} Validator.prototype = {
constructor: Validator,
add: function(dom,rules) {
var self = this;
for(var i = 0, len = rules.length; i < len; i++) {
var rule = rules[i];
(function(rule){
var str = rule.strategy.split(":"); // minLength:6的场景
var fn = function() {
var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
str.unshift(dom.value); // 往数组str的第一位插入value值
str.push(rule.errorMsg);
return strategys[strategyType].apply(dom,str);
}
self.cache.push(fn);
})(rule)
}
},
start: function() {
for(var i = 0, fn; fn = this.cache[i++];) {
var msg = fn();
if(msg) {
return msg;
}
}
}
}

调用方式改变一下:

function submitValidate() {
var registerForm = document.getElementById("registerForm");
var validator = new Validator();
validator.add(registerForm.userName, [{
strategy: 'isNotEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:10',
errorMsg: '用户名长度不能小于10位'
}]);
validator.add(registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码的长度不能小于6位'
}]);
validator.add(registerForm.phoneNumber, [{
strategy: 'mobileFormat',
errorMsg: '手机号码格式不正确'
}]);
var resultMsg = validator.start();
if (resultMsg) {
alert(resultMsg);
return false;
}
return true;
}

完整的代码如下:

var strategys = {
isNotEmpty: function(value, errorMsg) {
if (value === '') {
return errorMsg;
}
},
// 限制最小长度
minLength: function(value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
// 手机号格式
mobileFormat: function(value, errorMsg) {
if (!/(^1[0-9]{10}$)/.test(value)) {
return errorMsg;
}
}
} function Validator() {
this.cache = []; // 保存效验规则
} Validator.prototype = {
constructor: Validator,
add: function(dom, rules) {
var self = this;
for (var i = 0, len = rules.length; i < len; i++) {
var rule = rules[i];
(function(rule) {
var str = rule.strategy.split(":"); // minLength:6的场景
var fn = function() {
var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
str.unshift(dom.value); // 往数组str的第一位插入value值
str.push(rule.errorMsg);
return strategys[strategyType].apply(dom, str);
}
self.cache.push(fn);
})(rule)
}
},
start: function() {
for (var i = 0, fn; fn = this.cache[i++];) {
var msg = fn();
if (msg) {
return msg;
}
}
}
} function submitValidate() {
var registerForm = document.getElementById("registerForm");
var validator = new Validator();
validator.add(registerForm.userName, [{
strategy: 'isNotEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:10',
errorMsg: '用户名长度不能小于10位'
}]);
validator.add(registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码的长度不能小于6位'
}]);
validator.add(registerForm.phoneNumber, [{
strategy: 'mobileFormat',
errorMsg: '手机号码格式不正确'
}]);
var resultMsg = validator.start();
if (resultMsg) {
alert(resultMsg);
return false;
}
return true;
}

当然我们也可以把验证各种类型的算法放到构造函数Validator原型上,这儿就不处理了。

关于表单校验的文章可参考:

总结

策略模式优点:

  • 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句。
  • 策略模式提供了开放-封闭原则,使代码更容易理解和扩展。
  • 策略模式中的代码可以复用。

参考

[JS设计模式]:策略模式及应用-计算奖金、表单验证的实现(5)的更多相关文章

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

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

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

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

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

    设计模式高大上,业务代码用不上...平时用不上我们就可以忽略了吗? 非也,就像面试造火箭,工作拧螺丝一样.万一我们公司哪天要造火箭了,你得立马能上手. 同时,有些复杂的业务代码也可以用设计模式的思想去 ...

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

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

  5. js函数、表单验证

    惊天bug!!!在script里面只要有一点点错误,就都不执行了!!!所以每写一个方法,就跑一下,因为这个书写疏忽导致的bug不可估量!!! [笑哭,所以我才这么讨厌js么,后来真心的是一点都不想再看 ...

  6. 表单验证插件-validator.js 使用教程

    做网站的时候,常常会涉及到各种表单验证.选择一款好用的表单验证插件,会降低表单验证开发的难度.在开发中,我目前使用的表单验证插件是:validator.js. validator.js 是一款轻量的表 ...

  7. jquery.validate.js使用之自定义表单验证规则

    jquery.validate.js使用之自定义表单验证规则,下面列出了一些常用的验证法规则 jquery.validate.js演示查看 jquery validate强大的jquery表单验证插件 ...

  8. java设计模式 策略模式Strategy

    本章讲述java设计模式中,策略模式相关的知识点. 1.策略模式定义 策略模式,又叫算法簇模式,就是定义了不同的算法族,并且之间可以互相替换,此模式让算法的变化独立于使用算法的客户.策略模式属于对象的 ...

  9. javascript 设计模式-----策略模式

    在<javascript设计模式>中,作者并没有向我们介绍策略模式,然而它却是一种在开发中十分常见的设计模式.最常见的就是当我们遇到一个复杂的表单验证的时候,常常需要编写一大段的if和el ...

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

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

随机推荐

  1. 疑难杂症(已解决) | 为什么出现python中tkinter创建界面需要一闪而过才进入主窗口?

    一.具体问题 如图所示,我编写了一个主窗口的程序(如下所示,有兴趣的可以自己复制后运行),发现需要先进入第一个窗口再进入主界面,这不符合逻辑. 代码区域(完整代码): from tkinter imp ...

  2. Git 清除缓存账密

    [已解决] git push 报错:git: 'credential-manager' is not a git command. See 'git --help'. 解决方案1)运行 git con ...

  3. SQL常用数据过滤---IN操作符

    在SQL中,IN操作符常用于过滤数据,允许在WHERE子句中指定多个可能的值.如果列中的值匹配IN操作符后面括号中的任何一个值,那么该行就会被选中. 以下是使用IN操作符的基本语法: SELECT c ...

  4. 学习笔记--Java中fpackage和import

    package和import 关于Java语言中的包机制: 包又称为package,Java中引入package主要是为了方便管理 怎么样定义 Java源程序的第一行编写package语句 packa ...

  5. Django model 层之聚合查询总结

    Django model 层之聚合查询总结 by:授客 QQ:1033553122 实践环境 Python版本:python-3.4.0.amd64 下载地址:https://www.python.o ...

  6. Windows系统解决VSCode终端无法输入问题

    最近重装了电脑系统(将原来的Win7装成Win10),重新安装了VSCode和git,也在VSCode里配置了git环境,但是在VSCode中的终端总是不显示.现记录下解决办法: 解决方法: 1.右键 ...

  7. canvas实现截图功能

    开篇 最近在做一个图片截图的功能. 因为工作时间很紧张, 当时是使用的是一个截图插件. 周末两天无所事事,来写一个简单版本的截图功能. 因为写的比较简单,如果写的不好,求大佬轻一点喷 读取图片并获取图 ...

  8. 1、Git简介

    1.1.概述 Git 是一个开源免费的分布式版本控制系统,用于快速高效地管理各种小型或大型项目的代码. Git 不仅容易学习.占用空间小,而且性能快如闪电. Git 具有廉价的本地分支.方便的暂存区域 ...

  9. 【RabbitMQ】10 深入部分P3 死信队列(交换机)

    1.死信交换机 说是死信队列,是因为RabbitMQ和其他中间件产品不一样 有交换机的概念和这个东西存在,别的产品只有队列一说 DeadLetterExchange 消息成为DeadMessage之后 ...

  10. 【Java-GUI】04 菜单

    --1.菜单组件 相关对象: MenuBar 菜单条 Menu 菜单容器 PopupMenu 上下文菜单(右键弹出菜单组件) MenuItem 菜单项 CheckboxMenuItem 复选框菜单项 ...