策略模式:以不变应万变

前言

不管什么书,你有没有发现它们都有一个共同点,就是不管一个知识点有多容易懂,作者都会用尽量多的文字来描述。我认为其中一个重要的原因就是从不同的角度来给你展现这个知识点,让你对所学知识点更加深刻,但有时候我并不这么认为,我觉得更好的做法是我给你讲完了一个套路,你可以自己去慢慢体会,理解透彻,然后再结合自己的实践工作进行尝试。我觉得把书写厚是体力活,把书写薄才是脑力活,不费话,跑题了。

什么是策略模式呢?我的理解是策略好比多拉A梦的法宝袋,当我有问题需要解决的时候我只需要从里面拿出一个对应的法宝来解决就好。策略就是我们预先想好,写好的规则,然后把这些不同规则封装成不同的方法放到一个容器里等待召唤,甚至这些方法还可以组合使用达到以不变应万变效果。

我觉得用一个表单验证的例子就可以把策略模式表达得足够到位了,而且这些场景在我们的实践开发中非常地实用,只要把策略写好,后面就可以说是信手拈来,远离传说中的码农称号又远了那么一丢丢。

策略模式之旅

ES5 版本

策略模式一般的套路如下:

新建一个策略库,比如这个库叫做:strategies

创建构造函数,比如叫:Validator

在 Validator 的原型上添加两个方法 add() 和 check()

代码如下:

var strategies = {
    isEmpty:function(value,message){
        if(!!!value){
            return message;
        }
    },
    minLength:function(value,length,message){
        var targetLen = value.length;
        if(targetLen < length){
            return message;
        }
    },
    isMobile:function(value,message){
        var phoneReg = /^\d{11}$/; // 这个匹配规则你自己可以根据实践情况来进行修改
        if(!phoneReg.test(value)){
            return message;
        }
    }
}

var Validator = function(){
    this.cache = [];
}

Validator.prototype.add = function(value,rules){
    var _this = this;
    for(var i=0,item;item = rules[i++];){
        (function(item){
            _this.cache.push(function(){
                var strategyArray = item["strategy"].split(":");
                var strategy = strategyArray.shift();
                strategyArray.unshift(value);
                strategyArray.push(item.message?item.message:"输入内容不合法!");
                return strategies[strategy].apply(null, strategyArray);
            })
        })(item);
    }
}

Validator.prototype.check = function(){
    for(var i=0,item;item = this.cache[i++];){
        var message = item();
        if(!!message){
            return message;
        }
    }
}

如何使用?我们在需要使用的地方创建一个 Validator 的实例,然后调用这个实例上的 add() 方法,添加表单验证规则,最后我们就可以在点提交表单前,执行一下实例的 check() 方法。 如果输入不合法,check() 方法会返回一个错误提示语,否则无返回。通过返回值是否存在就可以知道表单验证情况。只要有一个不合法就会直接返回当前不通过验证规则对应的提示语,最后你就可以根据这个来决定是否可以提交表单到后台。在项目中,你可以把这个封装一下供其它页面调用。

我们可以在页面中这样使用:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
</head>
<body>
<div id="app">
    <input v-model="val" type="text" id="test">
    <input v-model="val2" type="text" id="test2">
    <button @click="check()">提交</button>
</div>
<script>
var app = new Vue({
    el: '#app',
    data: {
    val: '',
    val2: '',
    validatorMessage:null
    },
    created(){

    },
    methods:{
        check(){
            this.validatorMessage = function(){
                var validator = new Validator();
                validator.add(this.val,[
                    {strategy:"isEmpty",message:"矮油,不允许为空!"},
                    {strategy:"minLength:5",message:"矮油,字符长度不合法!"}
                ]);
                validator.add(this.val2,[
                    {strategy:"isEmpty",message:"矮油,不允许为空!"},
                    {strategy:"isMobile",message:"矮油,这怎么可能是一个电话号码!"}
                ]);
                var result = validator.check();
                return result;
            }
            var status = this.validatorMessage();
            if(!!status){ // 这里就可以决定要不要把表单提交到后台
            	console.log("不可提交后台:"+ status);
            }else{
            	console.log("可提交后台!");
            }
        }
    }
})
</script>
</body>
</html>

当然,我这里是没有把策略封装到一个 js 文件中,你可以自己把它放到一个单独的文件中,只需要对外暴露 Validator 就好。

ES6 版本

下面是 ES6 版本的,并且放到了一个单独的 JS 文件中通过 export default 把 Validator 类暴露出去。

/*
* @Author: zhaoxixiong
* @Date:   2018-08-10 08:39:31
* @Last Modified by:   zhaoxixiong
* @Last Modified time: 2018-08-10 08:46:41
*/
let strategies = {
    isEmpty:function(value,message){
        if(!!!value){
            return message;
        }
    },
    minLength:function(value,length,message){
        var targetLen = value.length;
        if(targetLen < length){
            return message;
        }
    },
    isMobile:function(value,message){
        var phoneReg = /^\d{11}$/; // 这个匹配规则你自己可以根据实践情况来进行修改
        if(!phoneReg.test(value)){
            return message;
        }
    }
}

class Validator{
    constructor(){
        this.cache = [];
    }

    add(value,rules){
        for(let i=0,item;item = rules[i++];){
            this.cache.push(()=>{
                let strategyArray = item["strategy"].split(":");
                const strategy = strategyArray.shift();
                strategyArray.unshift(value);
                strategyArray.push(item.message?item.message:"输入内容不合法!");
                return strategies[strategy](...strategyArray);
            })
        }
    }

    check(){
        for(let i=0,item;item = this.cache[i++];){
            const message = item();
            if(!!message){
                return message;
            }
        }
    }
}

export default Validator

文件写好之后,我们可以在页面中这样使用它:

<template>
  <div class="home">
    <input v-model="val" type="text" id="test">
    <input v-model="val2" type="text" id="test2">
  </div>
</template>
<script>
import Validator from "@/utils/Validator"
export default {
  name: 'Home',
  data () {
    return {
      val:"",
      val2:""
    }
  },
  methods:{
    check(){
        this.validatorMessage = function(){
            var validator = new Validator();
            validator.add(this.val,[
                {strategy:"isEmpty",message:"矮油,不允许为空!"},
                {strategy:"minLength:5",message:"矮油,字符长度不合法!"}
            ]);
            validator.add(this.val2,[
                {strategy:"isEmpty",message:"矮油,不允许为空!"},
                {strategy:"isMobile",message:"矮油,这怎么可能是一个电话号码!"}
            ]);
            var result = validator.check();
            return result;
        }
        var status = this.validatorMessage();
        if(!!status){ // 这里就可以决定要不要把表单提交到后台
            console.log("不可提交后台:"+ status);
        }else{
            console.log("可提交后台!");
        }
    }
  }
}
</script>

使用 ES6 来编写里面的一些实现就变得更简单了。比如很多情况下我们通过箭头函数保证 this 的指向,而不用担心 this 另有所指,在 ES6 中我们也不需要使用 apply() 方法来进行传参了,而是使用 ES6 新语法 ... 操作符来展开数组实现把数组转化成参数的方式实现。

现在你是不是有点感觉了。