介绍

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

实现

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

  1. var validator = {
  2. validate: function(value,type) {
  3. switch(type) {
  4. case 'isNonEmpty':
  5. return true
  6. case 'isNumber':
  7. return true;
  8. case 'isAlphaNum':
  9. return true;
  10. default:
  11. return true;
  12. }
  13. }
  14. }
  15.  
  16. alert(validator.validate('123','isNonEmpty'))

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

  1. var validator = {
  2. types: { // 存放验证规则
  3. isNonEmpty: {
  4. validate: function(value) {
  5. return value !== ''
  6. },
  7. instructions: '传入的值不能为空'
  8. },
  9. isNumber: {
  10. validate: function(value) {
  11. return !isNaN(value);
  12. },
  13. instructions: '传入的值不是数字'
  14. },
  15. isAlphaNum: {
  16. validate: function(value) {
  17. return !/[^a-z0-9]/i.test(value)
  18. },
  19. instructions: '传入的值只能是数字或者字母,不能是特殊字符'
  20. }
  21. },
  22. config: {}, // 需要验证的类型
  23. messages: [], // 存放错误信息
  24. validate: function(data) { // 传入的data 为key-value的键值对
  25. var i, type, checker, resultOk;
  26. this.messages = []; // 首先清空错误信息
  27. for(i in data) {
  28. if(data.hasOwnProperty(i)) { // 判断i不是原型上的属性
  29. type = this.config[i]; // 获取验证类型
  30. if(!type) { // 没有当前校验类型直接跳过(不需要验证的)
  31. continue;
  32. }
  33. checker = this.types[type]; // 获取验证规则的验证类方法
  34. if(!checker) { // 验证规则类不存在 直接抛出异常
  35. throw {
  36. name: "ValidationError",
  37. message: "No handler to validate type " + type
  38. }
  39. }
  40. resultOk = checker.validate(data[i]);
  41. if(!resultOk) { // 验证不通过
  42. this.messages.push(checker.instructions);
  43. }
  44. }
  45. }
  46. return this.hasErrors();
  47. },
  48. hasErrors: function() {
  49. return this.messages.length !== 0;
  50. }
  51.  
  52. }

使用方式如下:

  1. var data = {
  2. firstName: '',
  3. lasName: 'shu',
  4. age: '',
  5. userName: 'tom shu'
  6. }
  7. validator.config = {
  8. firstName: 'isNonEmpty',
  9. age: 'isNumber',
  10. userName: 'isAlphaNum'
  11. }
  12.  
  13. validator.validate(data);
  14. if(validator.hasErrors()) {
  15. console.log(validator.messages.join("\n"));
  16. }
  17. // 结果:
  18. // 传入的值不能为空
  19. // 传入的值只能是数字或者字母,不能是特殊字符

其它策略模式示例

jquery中使用的animate方法

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

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

计算奖金

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

  1. var calculateBouns = function(salary,level) {
  2. if(level === 'A') {
  3. return salary * 4;
  4. }
  5. if(level === 'B') {
  6. return salary * 3;
  7. }
  8. if(level === 'C') {
  9. return salary * 2;
  10. }
  11. };
  12. // 调用如下:
  13. console.log(calculateBouns(4000,'A')); // 16000
  14. console.log(calculateBouns(2500,'B')); // 7500

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

使用策略模式代码如下:

  1. //代码如下:
  2. var obj = {
  3. "A": function(salary) {
  4. return salary * 4;
  5. },
  6. "B" : function(salary) {
  7. return salary * 3;
  8. },
  9. "C" : function(salary) {
  10. return salary * 2;
  11. }
  12. };
  13. var calculateBouns =function(level,salary) {
  14. return obj[level](salary);
  15. };
  16. console.log(calculateBouns('A',10000)); // 40000

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

表单校验

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

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

HTML代码如下:

  1. <form action="" id="registerForm" method="post" onsubmit="return submitValidate()">
  2. <p>
  3. <label>请输入用户名:</label>
  4. <input type="text" name="userName" />
  5. </p>
  6. <p>
  7. <label>请输入密码:</label>
  8. <input type="text" name="password" />
  9. </p>
  10. <p>
  11. <label>请输入手机号码:</label>
  12. <input type="text" name="phoneNumber" />
  13. </p>
  14. <div>
  15. <button type="submit">提交</button>
  16. </div>
  17. </form>

submitValidate验证方法如下:

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

缺点:

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

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

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

  1. var strategys = {
  2. isNotEmpty: function(value,errorMsg) {
  3. if(value === '') {
  4. return errorMsg;
  5. }
  6. },
  7. // 限制最小长度
  8. minLength: function(value,length,errorMsg) {
  9. if(value.length < length) {
  10. return errorMsg;
  11. }
  12. },
  13. // 手机号格式
  14. mobileFormat: function(value,errorMsg) {
  15. if(!/(^1[0-9]{10}$)/.test(value)) {
  16. return errorMsg;
  17. }
  18. }
  19. }

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

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

  1. function Validator() {
  2. this.cache = []; // 保存效验规则
  3. }
  4.  
  5. Validator.prototype = {
  6. constructor: Validator,
  7. add: function(dom,rule,errorMsg) {
  8. var str = rule.split(":"); // minLength:6的场景
  9. var fn = function() {
  10. var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
  11. str.unshift(dom.value); // 往数组str的第一位插入value值
  12. str.push(errorMsg);
  13. return strategys[strategyType].apply(dom,str);
  14. }
  15. this.cache.push(fn);
  16. },
  17. start: function() {
  18. for(var i = 0, fn; fn = this.cache[i++];) {
  19. var msg = fn();
  20. if(msg) {
  21. return msg;
  22. }
  23. }
  24. }
  25. }

调用方式:

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

完整的JS代码:

  1. var strategys = {
  2. isNotEmpty: function(value,errorMsg) {
  3. if(value === '') {
  4. return errorMsg;
  5. }
  6. },
  7. // 限制最小长度
  8. minLength: function(value,length,errorMsg) {
  9. if(value.length < length) {
  10. return errorMsg;
  11. }
  12. },
  13. // 手机号格式
  14. mobileFormat: function(value,errorMsg) {
  15. if(!/(^1[0-9]{10}$)/.test(value)) {
  16. return errorMsg;
  17. }
  18. }
  19. }
  20.  
  21. function Validator() {
  22. this.cache = []; // 保存效验规则
  23. }
  24.  
  25. Validator.prototype = {
  26. constructor: Validator,
  27. add: function(dom,rule,errorMsg) {
  28. var str = rule.split(":"); // minLength:6的场景
  29. var fn = function() {
  30. var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
  31. str.unshift(dom.value); // 往数组str的第一位插入value值
  32. str.push(errorMsg);
  33. return strategys[strategyType].apply(dom,str);
  34. }
  35. this.cache.push(fn);
  36. },
  37. start: function() {
  38. for(var i = 0, fn; fn = this.cache[i++];) {
  39. var msg = fn();
  40. if(msg) {
  41. return msg;
  42. }
  43. }
  44. }
  45. }
  46.  
  47. function submitValidate() {
  48. var registerForm = document.getElementById("registerForm");
  49. var validator = new Validator();
  50. validator.add(registerForm.userName,'isNotEmpty', '用户名不能为空');
  51. validator.add(registerForm.password,'minLength:6', '密码的长度不能小于6位');
  52. validator.add(registerForm.phoneNumber,'mobileFormat', '手机号码格式不正确');
  53. var resultMsg = validator.start();
  54. if(resultMsg) {
  55. alert(resultMsg);
  56. return false;
  57. }
  58. return true;
  59. }

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

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

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

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

  1. function Validator() {
  2. this.cache = []; // 保存效验规则
  3. }
  4.  
  5. Validator.prototype = {
  6. constructor: Validator,
  7. add: function(dom,rules) {
  8. var self = this;
  9. for(var i = 0, len = rules.length; i < len; i++) {
  10. var rule = rules[i];
  11. (function(rule){
  12. var str = rule.strategy.split(":"); // minLength:6的场景
  13. var fn = function() {
  14. var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
  15. str.unshift(dom.value); // 往数组str的第一位插入value值
  16. str.push(rule.errorMsg);
  17. return strategys[strategyType].apply(dom,str);
  18. }
  19. self.cache.push(fn);
  20. })(rule)
  21. }
  22. },
  23. start: function() {
  24. for(var i = 0, fn; fn = this.cache[i++];) {
  25. var msg = fn();
  26. if(msg) {
  27. return msg;
  28. }
  29. }
  30. }
  31. }

调用方式改变一下:

  1. function submitValidate() {
  2. var registerForm = document.getElementById("registerForm");
  3. var validator = new Validator();
  4. validator.add(registerForm.userName, [{
  5. strategy: 'isNotEmpty',
  6. errorMsg: '用户名不能为空'
  7. }, {
  8. strategy: 'minLength:10',
  9. errorMsg: '用户名长度不能小于10位'
  10. }]);
  11. validator.add(registerForm.password, [{
  12. strategy: 'minLength:6',
  13. errorMsg: '密码的长度不能小于6位'
  14. }]);
  15. validator.add(registerForm.phoneNumber, [{
  16. strategy: 'mobileFormat',
  17. errorMsg: '手机号码格式不正确'
  18. }]);
  19. var resultMsg = validator.start();
  20. if (resultMsg) {
  21. alert(resultMsg);
  22. return false;
  23. }
  24. return true;
  25. }

完整的代码如下:

  1. var strategys = {
  2. isNotEmpty: function(value, errorMsg) {
  3. if (value === '') {
  4. return errorMsg;
  5. }
  6. },
  7. // 限制最小长度
  8. minLength: function(value, length, errorMsg) {
  9. if (value.length < length) {
  10. return errorMsg;
  11. }
  12. },
  13. // 手机号格式
  14. mobileFormat: function(value, errorMsg) {
  15. if (!/(^1[0-9]{10}$)/.test(value)) {
  16. return errorMsg;
  17. }
  18. }
  19. }
  20.  
  21. function Validator() {
  22. this.cache = []; // 保存效验规则
  23. }
  24.  
  25. Validator.prototype = {
  26. constructor: Validator,
  27. add: function(dom, rules) {
  28. var self = this;
  29. for (var i = 0, len = rules.length; i < len; i++) {
  30. var rule = rules[i];
  31. (function(rule) {
  32. var str = rule.strategy.split(":"); // minLength:6的场景
  33. var fn = function() {
  34. var strategyType = str.shift(); // 删除str数组中的第一个元素并返回,即是获取校验规则的函数名
  35. str.unshift(dom.value); // 往数组str的第一位插入value值
  36. str.push(rule.errorMsg);
  37. return strategys[strategyType].apply(dom, str);
  38. }
  39. self.cache.push(fn);
  40. })(rule)
  41. }
  42. },
  43. start: function() {
  44. for (var i = 0, fn; fn = this.cache[i++];) {
  45. var msg = fn();
  46. if (msg) {
  47. return msg;
  48. }
  49. }
  50. }
  51. }
  52.  
  53. function submitValidate() {
  54. var registerForm = document.getElementById("registerForm");
  55. var validator = new Validator();
  56. validator.add(registerForm.userName, [{
  57. strategy: 'isNotEmpty',
  58. errorMsg: '用户名不能为空'
  59. }, {
  60. strategy: 'minLength:10',
  61. errorMsg: '用户名长度不能小于10位'
  62. }]);
  63. validator.add(registerForm.password, [{
  64. strategy: 'minLength:6',
  65. errorMsg: '密码的长度不能小于6位'
  66. }]);
  67. validator.add(registerForm.phoneNumber, [{
  68. strategy: 'mobileFormat',
  69. errorMsg: '手机号码格式不正确'
  70. }]);
  71. var resultMsg = validator.start();
  72. if (resultMsg) {
  73. alert(resultMsg);
  74. return false;
  75. }
  76. return true;
  77. }

当然我们也可以把验证各种类型的算法放到构造函数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. 国内版Unity 6 Preview编辑器无法切换到DX12的解决方案(6000.0.5f1c1已解决)

    先放解决方案的链接:https://www.cnblogs.com/horeaper/p/18200364 6000.0.0f1c1问题依旧,仍然是没有D3D12文件夹: 不仅新文件不加,旧文件(ha ...

  2. oeasy教您玩转vim - 60- # vim选项

    ​ vim选项 从头开始 这次我们从头开始 从进入vim之前开始 我们可以在终端里面给vim怎么样的参数呢? man vim 这个如果不行的话 要先运行unminimize更新manual 也可以在v ...

  3. 阅读翻译Mathematics for Machine Learning之2.5 Linear Independence

    阅读翻译Mathematics for Machine Learning之2.5 Linear Independence 关于: 首次发表日期:2024-07-18 Mathematics for M ...

  4. Docker 使用Docker创建MySQL容器

    使用Docker创建MySQL容器 实践环境 Docker version 20.10.5 MySQL5.7 Centos 7.8 创建步骤 1.拉取MySQL镜像 docker pull mysql ...

  5. 关于我自己的Gui界面库完善

    仓库地址:https://gitee.com/GPRO/Gui 功能说明:  解析XML, 接入AngleScript. 接下来需要做的: 因为有了WPF,MFC,HTML甚至UE5的使用经验,这里我 ...

  6. 【教程】解决npm 报错 npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` instead.

    问题描述 只要在控制台执行npm,不论有没有参数,都会有此警告: npm WARN config global `--global`, `--local` are deprecated. Use `- ...

  7. 【Hibernate】04 主键策略 & CRUD

    实体类编写规范: - 每个属性不应该被公开的访问,设置私有 - 提供可以访问和设置的方法,GETTER & SETTER - 必须编写一个主键属性[ID 唯一值] - 建议使用基本类型的包装类 ...

  8. 【Ubuntu】下载安装 20.04.版本 桌面端

    下载 Download 这次的是直接在界面上下载的,我都不知道为什么怎么点到之前的版本去了 12.04.5远古版本界面怪难看的... [下载地址:点我访问] https://cn.ubuntu.com ...

  9. 如何查看华为的大模型(AI模型),华为官方的mindspore下的大模型???

    由于华为官方的mindspore网站的设计比较反人性话,操作起来十分的复杂,因此如果想要在华为的官方网站上查找这个华为的官方大模型还是比较困难的,为此直接给出链接地址. PS. 要注意,华为的AI官方 ...

  10. 【转载】 日内瓦大学 & NeurIPS 2020 | 在强化学习中动态分配有限的内存资源

    原文地址: https://hub.baai.ac.cn/view/4029 ======================================================== [论文标 ...