4

本回内容介绍

上一回,聊了聊链式编程,模拟了jQuery和underscore.js,并写了一个遍历多维数组的函数。
介一回,聊策略模式(Strategy),策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

1. 策略模式

先看一个计算奖金的例子,这个在网上的策略模式例子里曝光率很高,如下:

var a = window.alert;
// 定义一个年终奖策略
var strategy = {
    // 传入的是工资,A级别的工资最高,倍数最多
    "A": function(salary) {
        return salary * 5;
    },
    "B": function(salary) {
        return salary * 3;
    },
    "C": function(salary) {
        return salary * 1;
    }
};

// 门面模式记得吧,系列08聊过的,这里就是接收level级别,salary薪资,返回年终奖
function Bonus(level, salary) {
    return strategy[level](salary);
};

// 测试
a(Bonus("C", 10000));    // 10000
a(Bonus("B", 20000));    // 60000
a(Bonus("A", 30000));    // 150000

策略对象封装了具体的算法,然后一个方法接收传参,委托给策略对象进行计算。这个例子很容易理解,很直观。

2. 策略模式之人在囧途

大家都看过电影《人在囧途》吧,两人先是乘飞机,然后乘火车,最后乘汽车,转换为代码如下:

var a = window.alert;
// 接口类,不清楚的盆友请看系列05接口
var TravelStrategy = new Interface('TravelStrategy',['journey']);

// 飞机类策略
var AirplanelStrategy = function (){
    // 检测是否实现了接口方法
    Interface.ensureImplements(this,TravelStrategy);
}
AirplanelStrategy.prototype = {
    // 还原指针指向本类
    constructor:AirplanelStrategy,
    // 行程策略,这里的策略不具体实现,就弹出一个对白吧
    journey:function(){
        a("大卫第一次坐飞机,登机前喝光了一箱牛奶~");
    }
}

// 火车类策略
var TrainStrategy = function (){
    Interface.ensureImplements(this,TravelStrategy);
}
TrainStrategy.prototype = {
    constructor:TrainStrategy,
    journey:function(){
        a("大卫第一次坐火车,站票~");
    }
}

// 汽车类策略
var BusStrategy = function (){
    Interface.ensureImplements(this,TravelStrategy);
}
BusStrategy.prototype = {
    constructor:BusStrategy,
    journey:function(){
        a("大卫第一次坐汽车,兴奋不已,大唱回家过年~");
    }
}

// 定义一个人的类,其实这个就是上下文环境类,每一次对Strategy对象的请求中都将这个状态传递过去
function Person(travelStrategy ){
    this.strategy = travelStrategy;
}

Person.prototype = {
    constructor:Person,
    setTravelStrategy:function(travelStrategy){
        this.strategy = travelStrategy;
    },
    travel:function(){
        this.strategy.journey();
    }
}

测试部分代码如下:

// 测试部分,没错,又是大卫登场了,首先,大卫囧途,NO1:乘坐飞机回家
var david = new Person(new AirplanelStrategy());  
david.travel();  

// NO2:改策略乘坐火车回家
david = new Person(new TrainStrategy());  
david.travel();  
  
// NO3:该策略乘坐汽车回家  
david.setTravelStrategy(new BusStrategy());  
david.travel(); 

这个例子是根据一个java的策略模式例子改的JS版本,好玩儿吧,又玩儿了一次大卫哥,以后一看到策略模式就会想起“卫囧”。

3. 策略模式之validate

有了上面两个例子的基础,对策略模式有了更深的理解,那介一个例子就来模拟validate,代码如下:
html界面部分

<!doctype html>
<html>
<head>
    <title>听飞狐聊设计模式之策略模式</title>
</head>
<style>
.middle{
    text-align:center;
}
.title{
    background-color:black;
    color:#f3e14f;
    display:inline-block;
    font-size:18px;
    height:40px;
    line-height:40px;
    padding:0 50px;
    cursor:pointer;
}
</style>
<body>

<form id="sub" class="middle">
    <input type="text" name='username' />
    <br><br>
    <input type="password" name='pwd' />
    <div class="middle">
        <h5 class="title" id='btn'>登 录</h5>
    </div>
</form>
<script src='./strategy.js'></script>
</body>
</html>

策略对象部分:

// 策略对象
var strategys = {
    // 判断是否为空,传参为dom元素的值,错误信息
    isNotEmpty: function(value,errMsg) {
        // 值为空,则返回传入的提示信息
        if(value === '') {
            return errMsg;
        }
    },
    // 限制最小长度,如果不符合
    minLength: function(value,length,errMsg) {
        // 传入的dom元素值的长度小于定义的长度,则返回错误信息
        if(value.length < length) {
            return errMsg;
        }
    }
};

下面是校验类的代码,如下:

// 定义验证类
var Validator = function(){
    // 缓存验证策略
    this.cache = [];  
};

Validator.prototype = {
    constructor:Validator,
    // 传入的dom为元素的值,rules为验证规则
    add:function(dom,rules) {
        var self = this;
        // 遍历验证规则数组
        for(var i=0,len=rules.length;i<len;i++){
            // 闭包
            (function(i){
                // 先看下面的测试代码里的验证规则数组,然后再接着看这里的代码
                var strategyAry = rules[i].strategy.split(":");
                // 获取提示信息
                var errMsg = rules[i].errMsg;
                // 存入缓存数组
                self.cache.push(function(){
                    // strategy是每一个规则项目,如:strategy,minLength
                    var strategy = strategyAry.shift();
                    // 元素值放到首项
                    strategyAry.unshift(dom.value);
                    // 提示信息放入strategyAry的最后
                    strategyAry.push(errMsg);
                    // 调用策略类,apply让指针指向dom本身
                    return strategys[strategy].apply(dom,strategyAry);
                });
            })(i);
        }
    },
    // 执行校验函数
    run:function(){
        for(var i=0,len=this.cache.length;i<len;i++) {
            // 取得效验后的返回信息
            var msg = this.cache[i]();
            if(msg) {
                return msg;
            }
        }
    }
}

测试部分代码,如下:

// 代码调用
var btn = document.getElementById("btn");
var sub = document.getElementById("sub");
// 校验环境类
function validateContext(){
    // 创建Validator对象
    var validator = new Validator();
    // 添加一些效验规则
    validator.add(sub.username,[
        {strategy: 'isNotEmpty',errMsg:'用户名不能为空'},
        {strategy: 'minLength:6',errMsg:'用户名长度不能小于6位'}
    ]);
    validator.add(sub.pwd,[
        {strategy: 'minLength:6',errMsg:'密码长度不能小于6位'},
    ]);
    // 获取效验结果
    var errMsg = validator.run();
    // 返回效验信息
    return errMsg;
};
// 点击确定提交
btn.addEventListener('click',function(){
    var errMsg = validateContext();
    // 如果有提示信息则直接提示
    if(errMsg){
        alert(errMsg);
        return false;
    // 否则errMsg为undefined,那么跳转到飞狐系列,嘿嘿~
    }else{
        var url = 'http://segmentfault.com/blog/%E9%A3%9E%E7%8B%90';
        window.location.href = url;
    }
},false)

这里就是模拟validator的简版代码了,我没有写太多方法,大家可以扩充。这个例子更贴近实际开发,相信对策略模式会有更深的理解。

装逼图
装个逼,刚策略模式聊到了人在囧途,突然就想起了《唐人街探案》,其中肖央演的坤泰确实很出彩~~

这一回聊的策略模式,模拟了下validator,难度适中~
下面的内容是一道题,记得系列03里的某客面试题吗,还有一道题是数字转中文, 例:123转一百二十三~

JS数字转中文

中国古代表示数量的单位依次是:个、十、百、千、万、亿、兆、京、垓、秭...这里的单位我就直接copy了。

function convert(num){
    // cn和unit是单位,unit我这里分为两组
    var cn      = ['零','一','二','三','四','五','六','七','八','九'];
    var unit = ['十','百','千'],bigUnit = ['','万','亿','兆','京','垓','杼','穰','沟','涧','正','载'];
    var str  = '';        // 转换字符串
    var arr  = [];        // 结果数组
    var index = 0;        // 单位索引
    var r = [];            // result缩写
    
    if(isNaN(num)){
        throw new Error('输入数字有误,请重新输入数字!');
    }
    
    if(typeof num === 'number')    str = str+num;                // 数字转字符串
    else if(typeof num === 'string') str = num;
    
    if(str.length>0){
        // 判断如果有小数,则取左边整数
        if(str.indexOf('.')) str = str.split('.')[0];        
        // 拆分字符串到数组
        arr = str.split('');
        // 排除开头为0的情况
        while(arr.indexOf('0')==0){
            arr.shift();
        }                                
    }
    
    // 补位,不满4位就补足4位,如:10->0010;126->0126
    var fill = str.length%4;
    if(fill != 0 ){
        fill = 4 - str.length%4;
        for(var i=0;i<fill;i++){
            arr.unshift(0);
        }
    }
    
    str = '';        
    // 遍历数字
    for(var i=0;i<arr.length;i++){
        // 数字转中文
        str += cn[arr[i]];                                    
    }
    return str;    
}

var ins = '126900212100223423234242120010';
alert(convert(ins));//零零一二六九零零二一二一零零二二三四二三二三四二四二一二零零一零

这里就已经可以实现数字转中文了,继续写带有单位的计算转换,接着敲代码吧^_^。

JS数字转中文进阶版

上面例子的基础上,再做每四位一组,遍历单位转换,再加一个判断零的情况,代码如下:

function convert(num){
    // cn和unit是单位,unit我这里分为两组
    var cn      = ['零','一','二','三','四','五','六','七','八','九'];
    var unit = ['十','百','千'],bigUnit = ['','万','亿','兆','京','垓','杼','穰','沟','涧','正','载'];
    var str  = '';        // 转换字符串
    var arr  = [];        // 结果数组
    var index = 0;        // 单位索引
    var r = [];            // result缩写
    
    if(isNaN(num)){
        throw new Error('输入数字有误,请重新输入数字!');
    }
    
    if(typeof num === 'number')    str = str+num;                // 数字转字符串
    else if(typeof num === 'string') str = num;
    
    if(str.length>0){
        // 判断如果有小数,则取左边整数
        if(str.indexOf('.')) str = str.split('.')[0];        
        // 拆分字符串到数组
        arr = str.split('');
        // 排除开头为0的情况
        while(arr.indexOf('0')==0){
            arr.shift();
        }                                    
    }
    
    // 补位,不满4位就补足4位,如:10->0010;126->0126
    var fill = str.length%4;
    if(fill != 0 ){
        fill = 4 - str.length%4;
        for(var i=0;i<fill;i++){
            arr.unshift(0);
        }
    }
    
    str = '';        
    // 遍历数字
    for(var i=0;i<arr.length;i++){
        // 数字转中文
        str += cn[arr[i]];                                    
    }
    
    // 分组
    var index = str.length/4;
    if(index>0){
        var o = {};
        // 按照四位一组
        for(var i=0;i<index;i++){
            o[i] = str.substr(i*4,4);
            arr = o[i].split('');
            r.push(arr);
            
        }
        // 遍历结果数组,然后排十,百,千单位
        r.forEach(function(item,i,arr){
            item[0]=item[0]+unit[2];
            item[1]=item[1]+unit[1];
            item[2]=item[2]+unit[0];
        });
        // 反转数组,遍历,排万,亿等大单位
        r.reverse().forEach(function(item,i,arr){
            arr[i].push((bigUnit[i]))
        });
        // 再反转数组
        r.reverse();
        // 这里判断零的情况
        for(var i=0,len=r.length;i<len;i++){
            for(var j=0;j<r[i].length;j++){
                if(r[i][j].substr(0) == "零十"&&r[i][j+1].substr(0) != "零"){
                    r[i][j] = '零';
                }else if(r[i][j].substr(0) == "零十"&&r[i][j+1].substr(0) == "零"){
                    r[i][j] = '';
                }else if(r[i][j].substr(0,2) == "零"){
                    r[i][j] = '';
                }else if(r[i][j].substr(0,2) == "零千"||r[i][j].substr(0,2) == "零百"){
                    r[i][j] = '';
                }
            }
        }
        
        str = r.join().replace(/,/g,'');
        return str;
    }
}

var ins = '126900212100223423234242120010';
alert(convert(ins));//一十二穰六千九百杼二千一百二十一垓二十二京三千四百二十三兆二千三百四十二亿四千二百一十二万一十

这就是完整的代码了,我没有做更多的测试,只能算是粗略的完成了,确实是简单粗暴的敲完了代码,其实再完美一点,在判断零的时候还可以计算,整体代码还可以优化算法,更简单,性能更优化,抛砖引玉,这个就给大家继续完善了,优化了这个例子的童鞋,也分享分享呗,可以直接评论,多碰撞碰撞,大家一起进步呗,嘿嘿~~

这一回,主要策略模式,模拟了validate,做了一道题数字转中文的题。
下一回,聊一聊JS的享元模式。

客观看完点个赞,推荐推荐呗,嘿嘿~~


注:此系飞狐原创,转载请注明出处


飞狐
2.4k 声望1.4k 粉丝

专注AI量化,技术界最懂金融的CFA金融分析师