[JS设计模式]:策略模式及应用-计算奖金、表单验证的实现(5)
介绍
策略模式的意义是定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。此模式让算法的变化不会影响到使用算法的客户。
实现
举一个例子,比如我们做数据合法性校验,一般是通过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条件语句。
- 策略模式提供了开放-封闭原则,使代码更容易理解和扩展。
- 策略模式中的代码可以复用。
参考
- 深入理解JavaScript系列(33):设计模式之策略模式
- Javascript常用的设计模式详解-八:理解javascript中的策略模式
- 腾讯全端 AlloyTeam 团队 Blog-【Javascript 设计模式 9】-策略模式
[JS设计模式]:策略模式及应用-计算奖金、表单验证的实现(5)的更多相关文章
- js 设计模式——策略模式
策略模式(Strategy) 定义:将定义的一组算法封装起来,使其相互之间可以替换.封装的算法具有一定的独立性,不会随客户端的变化而变化 废话不多说,先来个例子 // 例如要写一个计算两个数加减乘除的 ...
- [转]js设计模式-策略模式
在程序设计中,常常遇到类似的情况,要实现某一个功能有多种方案可以选择.比如一个压缩文件的程序,既可以选择zip算法,也可以选择gzip算法.这些算法灵活多样,而且可以随意互相替换.这种解决方案就是本文 ...
- JS设计模式——策略模式
设计模式高大上,业务代码用不上...平时用不上我们就可以忽略了吗? 非也,就像面试造火箭,工作拧螺丝一样.万一我们公司哪天要造火箭了,你得立马能上手. 同时,有些复杂的业务代码也可以用设计模式的思想去 ...
- js设计模式--策略模式
策略模式: 定义了一系列的算法,把他们封装起来,是它们之间可以互相替换,此模式不会影响到使用算法的客户. 回忆下jquery里的animate方法: $( div ).animate( {" ...
- js函数、表单验证
惊天bug!!!在script里面只要有一点点错误,就都不执行了!!!所以每写一个方法,就跑一下,因为这个书写疏忽导致的bug不可估量!!! [笑哭,所以我才这么讨厌js么,后来真心的是一点都不想再看 ...
- 表单验证插件-validator.js 使用教程
做网站的时候,常常会涉及到各种表单验证.选择一款好用的表单验证插件,会降低表单验证开发的难度.在开发中,我目前使用的表单验证插件是:validator.js. validator.js 是一款轻量的表 ...
- jquery.validate.js使用之自定义表单验证规则
jquery.validate.js使用之自定义表单验证规则,下面列出了一些常用的验证法规则 jquery.validate.js演示查看 jquery validate强大的jquery表单验证插件 ...
- java设计模式 策略模式Strategy
本章讲述java设计模式中,策略模式相关的知识点. 1.策略模式定义 策略模式,又叫算法簇模式,就是定义了不同的算法族,并且之间可以互相替换,此模式让算法的变化独立于使用算法的客户.策略模式属于对象的 ...
- javascript 设计模式-----策略模式
在<javascript设计模式>中,作者并没有向我们介绍策略模式,然而它却是一种在开发中十分常见的设计模式.最常见的就是当我们遇到一个复杂的表单验证的时候,常常需要编写一大段的if和el ...
- 15. 星际争霸之php设计模式--策略模式
题记==============================================================================本php设计模式专辑来源于博客(jymo ...
随机推荐
- 疑难杂症(已解决) | 为什么出现python中tkinter创建界面需要一闪而过才进入主窗口?
一.具体问题 如图所示,我编写了一个主窗口的程序(如下所示,有兴趣的可以自己复制后运行),发现需要先进入第一个窗口再进入主界面,这不符合逻辑. 代码区域(完整代码): from tkinter imp ...
- Git 清除缓存账密
[已解决] git push 报错:git: 'credential-manager' is not a git command. See 'git --help'. 解决方案1)运行 git con ...
- SQL常用数据过滤---IN操作符
在SQL中,IN操作符常用于过滤数据,允许在WHERE子句中指定多个可能的值.如果列中的值匹配IN操作符后面括号中的任何一个值,那么该行就会被选中. 以下是使用IN操作符的基本语法: SELECT c ...
- 学习笔记--Java中fpackage和import
package和import 关于Java语言中的包机制: 包又称为package,Java中引入package主要是为了方便管理 怎么样定义 Java源程序的第一行编写package语句 packa ...
- Django model 层之聚合查询总结
Django model 层之聚合查询总结 by:授客 QQ:1033553122 实践环境 Python版本:python-3.4.0.amd64 下载地址:https://www.python.o ...
- Windows系统解决VSCode终端无法输入问题
最近重装了电脑系统(将原来的Win7装成Win10),重新安装了VSCode和git,也在VSCode里配置了git环境,但是在VSCode中的终端总是不显示.现记录下解决办法: 解决方法: 1.右键 ...
- canvas实现截图功能
开篇 最近在做一个图片截图的功能. 因为工作时间很紧张, 当时是使用的是一个截图插件. 周末两天无所事事,来写一个简单版本的截图功能. 因为写的比较简单,如果写的不好,求大佬轻一点喷 读取图片并获取图 ...
- 1、Git简介
1.1.概述 Git 是一个开源免费的分布式版本控制系统,用于快速高效地管理各种小型或大型项目的代码. Git 不仅容易学习.占用空间小,而且性能快如闪电. Git 具有廉价的本地分支.方便的暂存区域 ...
- 【RabbitMQ】10 深入部分P3 死信队列(交换机)
1.死信交换机 说是死信队列,是因为RabbitMQ和其他中间件产品不一样 有交换机的概念和这个东西存在,别的产品只有队列一说 DeadLetterExchange 消息成为DeadMessage之后 ...
- 【Java-GUI】04 菜单
--1.菜单组件 相关对象: MenuBar 菜单条 Menu 菜单容器 PopupMenu 上下文菜单(右键弹出菜单组件) MenuItem 菜单项 CheckboxMenuItem 复选框菜单项 ...