介绍

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

实现

举一个例子,比如我们做数据合法性校验,一般是通过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. Git的存储原理

    目录 Git 设计原理 Git vs SVN Git 存储模型 .git 目录结构 Git 基本数据对象 Git 包文件 Git 引用 Git 设计原理 概括的讲,Git 就是一个基于快照的内容寻址文 ...

  2. nats 简介和使用

    nats 简介和使用 nats 有 3 个产品 core-nats: 不做持久化的及时信息传输系统 nats-streaming: 基于 nats 的持久化消息队列(已弃用) nats-jetstre ...

  3. k8s 环境搭建(2)

    安装docker组件 配置本地源或者自带的网络源2选1 1.切换镜像源 wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce ...

  4. 查询当前网段的所有在用IP

    查询当前网段的所有在用IP For /L %i in (0,1,254) DO ping 192.168.10.%i >>D:\IP.txt https://www.cnblogs.com ...

  5. Mysql函数1-IFNULL

    IFNULL函数用于判断参数值是null时则返回指定内容. 原本 select goods_base_name,goods_id from goods where goods_id in (6,7,8 ...

  6. 测试工程师-年终总结PPT

    2022年年终总结-xxx 一.首页 2022年年终总结暨2023年工作计划 汇报人:测试组-xxx 日期: 2023.1.13 二.目录 1.年度工作概述 2.工作亮点展示 3.持续精进点 4.明年 ...

  7. 【Tutorial C】04 基本输入输出

    输出单个字符 putchar('a'); // 字符输出函数,其功能是在终端(显示器)输出单个字符. putchar('\n'); // 支持转义换行 putchar(77); // 可以直接注入AS ...

  8. 【Vue】Re11 Vue 与 Webpack

    一.案例环境前置准备: 创建一个空目录用于案例演示 mkdir vue-sample 初始化案例和安装webpack cd vue-sample npm install webpack@3.6.0 - ...

  9. mojo编程语言:mojo调用python库及内置函数builtins

    编程语言mojo调用python十分方便,mojo不仅可以调用python的库函数更可以调用python的内置函数(builtins),给出示例代码: from python import Pytho ...

  10. 局域网环境下,如何在Ubuntu中发现Windows10下的共享文件夹

    参考: https://blog.csdn.net/rangfei/article/details/124225799 ======================================== ...