【JavaScript设计模式】行为型设计模式--策略模式

来源:互联网 发布:mac文件打包怎么做 编辑:程序博客网 时间:2024/06/05 07:46

       俗话说“条条大路通罗马”。在现实中,很多时候也有多种途径可以到达同一个目的地,比如,我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路。在程序设计中,我们也常常遇到类似的情况,要实现某一个功能有多种方案可以选择。比如要对一个数组进行排序,我们可以选择快速排序算法,也可以选择冒泡排序算法。

       策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。当然这些算法灵活多样,而且可以随意相互替换。这种方案呢,就是本文将要介绍的策略模式。

      贴上策略模式的官方定义:

      策略模式指的是定义一些列的算法,把他们一个个封装起来,并且使他们可以相互替换。目的就是将算法的使用与算法的实现分离开来。

     从定义上看,策略模式就是用来封装算法的。但如果把策略模式仅仅用来封装算法,未免有点大材小用。在实际开发中,我们通常会把算法的含义扩散开来,使策略模式也可以用来封装一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以使用策略模式来封装他们。

     接下来想用一个表单校验的算法进行说明这个模式。

     在一个Web项目中,注册、登录、修改用户信息等功能的实现都离不开提交表单。

     假设我们正在编写一个注册的页面,在点击注册之前,有如下几条校验逻辑。

  • 用户名不能为空;
  • 密码长度不能少于6位;
  • 手机号码必须符合格式。
1、表单校验的第一个版本
<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>register</title></head><body><form id="registerForm" method="post" action="http://###.com/register">    <label for="userName">请输入用户名:</label>    <input type="text" name="userName" id="userName">    <label for="pwd">请输入密码:</label>    <input type="text" name="password" id="pwd">    <label for="phoneNumber">请输入密码:</label>    <input type="text" name="phoneNumber" id="phoneNumber">    <input type="button" value="提交"></form><script>    var registerForm = document.getElementById("registerForm");    registerForm.onsubmit = function(){        if( registerForm.userName.value === ''){            alert("用户名不能为空!");            return false;        }        if( registerForm.pwd.value.length < 6){            alert("密码长度不能少于6位!");            return false;        }        if( !/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)){            alert("手机号码格式不正确!");            return false;        }    }</script></body></html>
解析:
      1) registerForm.onsubmit 函数比较庞大,包含了很多if语句,这些语句需要覆盖所有的校验规则;
      2) registerForm.onsubmit 函数缺乏弹性,如果增加了一个新的校验规则,或者想把密码的长度校验从6变成8,我们都必须深入这个函数的内部实现,这违反了开放-封闭原则;
      3)算法的复用性差,如果程序中增加另外一个表单,比如登录,这个表单也需要进行一些类似的校验,那么我们很可能只是复制一下这段代码,然后相对应的进行修改,造成的后果就是相似的代码到处都有,不利于代码的维护与美观。

     那么,为了解决以上出现的这些问题,我们可以采用策略模式重构这个表单校验。
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;            }        }    };
       接下来,我们需要实现Validator类,负责接收用户的请求并委托给strategy对象。在实现Validator类的代码之前,我们先了解下用户是如何向Validator类发送请求的。
    var validataFunc = function () {        var validator = new Validator(); //创建一个Validator对象        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;  //阻止表单提交        }    };
解析:
  1. 我们首先创建了一个 Validator对象,然后通过validator.add()方法往对象中添加一些校验规则,这个方法接受3个参数。
  2. validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空'); 其中registerForm.userName为参与校验的input输入框; 'isNonEmpty'为名字的校验参数;'用户名不能为空'是当校验未通过时返回的错误信息。
最后一部分是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,validataFunc;validataFunc = this.cache[ i++]; ){            var msg = validataFunc();            if(msg){                return msg;            }        }    };

     使用策略模式重构代码之后,我们仅仅通过“配置”的方式就可以完成一个表达的校验,而且这些校验规则也可以复用在程序中的其他需要校验的地方。在修改某个校验规则的时候,只需要编写或者该写少量的代码。
3、总结
      策略模式定义了一系列算法,从概念上来说,所有的这些算法都是做相同的事情,只是实现不同,他可以以相同的方式调用所有的方法,减少了各种算法类与使用算法类之间的耦合。
      从另外一个层面上来说,单独定义算法类,也方便了单元测试,因为可以通过自己的算法进行单独测试。
      实践中,不仅可以封装算法,也可以用来封装几乎任何类型的规则,是要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑是要策略模式来处理各种变化。
优点:
  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句;
  • 策略模式提供了对开放-封闭原则的完美支持,将算法进行封装,使得它们易于切换,易于理解和易于扩展;
  • 策略模式的代码可以复用在代码的其他地方,从而避免许多重复的复制黏贴工作,也使得代码更简洁。