策略模式在表单验证中的应用

在任何一个系统里面,几乎都缺少不了提交表单的过程,比如注册、登陆、下订单等等。这篇文章就阐述了如何编写看着舒服的表单验证代码。

1、问题

相信大家平时再做表单验证的时候基本与如下代码相同:

if(input1.value === ''){
    alert('xxx不能为空');
    return false;
}else if(input2.length < 6){
    alert('xxx最少6个字符');
    return false;
}else if(...){
    ...
    return false;
}

这样编写代码,的确能够完成业务的需求,能够完成表单的验证,但是存在很多问题,比如:

1、代码中包含太多的 if - else 语句,如果验证规则较多的话,看上去太过于恶心……

2、验证规则不可复用,如果我们另外一个页面也需要验证相似的表单,我们唯一的做法就是,复制一份,粘贴过去……

3、代码不易维护,不需要解释了吧……

2、解决办法

所谓办法总比问题多,办法是有的,比如马上要讲解的使用 策略模式 使表单验证更优雅更完美,我相信很多人很抵触设计模式,一听设计模式就觉得很遥远,觉得自己在工作中很少用到设计模式,那么你就错了,特别是js这种灵活的语言,有的时候你已经在你的代码中使用了设计模式,只是你不知道而已。更多关于设计模式的东西,以后会陆续写博客描述,这里只希望大家抛弃设计模式神秘的感觉,通俗的讲,它无非就是完成一件事情通用的办法而已。

3、畅想

回到正题,假如我们不想使用过多的 if - else 语句,那么我们心中比较理想的代码编写方式是什么呢?我们能不能像编写配置一样的去做表单验证呢?再来一个”一键验证“的功能,是不是很爽?答案是肯定的,所以我们心中理想的编写代码的方式如下:


// 获取表单form元素
var form = document.getElementById('f1');

// 创建表单校验实例
var validation = new Formvalidation(VerifiPolicy);
// 编写校验配置
validation.add(form.username, 'isNoEmpty', '用户名不能为空');
validation.add(form.password, 'minLength: 6', '密码长度不能小于6个字符');
validation.add(form.code, 'isMobile', '请填写正确的手机号');

// 开始校验,并接收错误信息
var errorMsg = validation.start();

// 如果有错误信息输出,说明校验未通过
if(errorMsg){
    // 做一些其他的事
    return false;
}

怎么样?感受感受,是不是看上去优雅多了?好了,有了心中的畅想,我们就可以向目标迈进了,下一步就要了解了解什么事策略模式了。

4、策略模式

策略模式,单纯的看它的名字”策略“,指的是做事情的方法,比如你从北京到哈尔滨(为什么到哈尔滨呢?因为我老家是哈尔滨的[偷笑]),你可以有几种策略供选择:

1、飞机,嗖嗖嗖直接就到了,节省时间。
2、火车,可以选择高铁出行,专为飞机恐惧症者提供。
3、徒步,不失为一个锻炼身体的选择。
4、other method……

所以,做一件事你会有很多方法,也就是所谓的策略,而我们今天要讲的策略模式也就是这个意思,它的核心思想是,将做什么和谁去做相分离。所以,一个完整的策略模式要有两个类,一个是策略类,一个是环境类(主要类),环境类接收请求,但不处理请求,它会把请求委托给策略类,让策略类去处理,而策略类的扩展是很容易的,这样,使得我们的代码易于扩展。

在表单验证的例子中,各种验证的方法组成了策略类,比如:判断是否为空的方法(如:isNoEmpty),判断最小长度的方法(如:minLength),判断是否为手机号的方法(isMobule)等等,他们组成了策略类,供给环境类去委托请求。下面,我们就来实战一下。

5、编写策略类

策略类很简单,它是由一组验证方法组成的对象,即策略对象

// 策略对象
var VerifiPolicy = {
    // 判断是否为空
    isNoEmpty : 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;
        }
    }
    // 其他
}

6、编写环境类

根据我们的畅想,我们使用add方法添加验证配置,如下:

validation.add(form.username, 'isNoEmpty', '用户名不能为空');

add方法接受三个参数,第一个参数是表单字段,第二个参数是策略对象中策略方法的名字,第三个参数是验证未通过的错误信息。

然后使用 start 方法开始验证,若验证未通过,返回验证错误信息,如下:

var errorMsg = validation.start();

另外,再解释一下下面这句代码:

validation.add(form.password, 'minLength: 6', '密码长度不能小于6个字符');

add方法第一个参数我们说过了,是要验证的表单元素,第二个参数是一个字符串,使用 冒号(:) 分割,前面是策略方法名称,后面是传给这个方法的参数,第三个参数仍然是错误信息,我们希望完成这样的代码,废话不多说,直接上代码,如下:

// 构造函数
var Formvalidation = function(VerifiPolicy){
    // 保存策略对象
    this.strategies = VerifiPolicy;
    // 验证缓存
    this.validationFns = [];
}

// add 方法
Formvalidation.prototype.add = function(dom, rule, errorMsg){

    var ary = rule.split(':');
    var arg = [];
    var self = this;
    this.validationFns.push(function(){
        arg = [];    // 重置参数
        var ruleName = ary[0];    // 策略对象方法名

        // 组装参数
        arg.push(dom.value);
        if(ary[1]){
            arg.push(ary[1]);
        }
        arg.push(errorMsg);

        // 调用策略函数
        return self.strategies[ruleName].apply(dom, arg);
    });
}

// 开始验证
Formvalidation.prototype.start = function(){
    for(var i = 0; ; i++){
        var msg = this.validationFns[i]();
        if(msg){
            return msg;
        }
    }
}

至此,一个最简单的应用就完成了,另外你还可以对这个环境类进行扩展,比如支持复选框、单选框等表单的验证,还可以支持一个表单多规则的验证方式,总之,只要你觉得可以,就ok。程序世界从来都欢迎创造,去发挥你的想象吧。