上一回,聊了聊链式编程,模拟了jQuery和underscore.js,并写了一个遍历多维数组的函数。
介一回,聊策略模式(Strategy),策略模式定义了一系列的算法,并将每个算法封装起来,并且使它们还能够相互替换。策略模式让算法独立于使用它的客户而独立变化。javascript
先看一个计算奖金的例子,这个在网上的策略模式例子里曝光率很高,以下:html
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
策略对象封装了具体的算法,而后一个方法接收传参,委托给策略对象进行计算。这个例子很容易理解,很直观。java
你们都看过电影《人在囧途》吧,两人先是乘飞机,而后乘火车,最后乘汽车,转换为代码以下:面试
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版本,好玩儿吧,又玩儿了一次大卫哥,之后一看到策略模式就会想起“卫囧”。编程
有了上面两个例子的基础,对策略模式有了更深的理解,那介一个例子就来模拟validate,代码以下:
html界面部分segmentfault
<!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转一百二十三~
中国古表明示数量的单位依次是:个、10、百、千、万、亿、兆、京、垓、秭...这里的单位我就直接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));//零零一二六九零零二一二一零零二二三四二三二三四二四二一二零零一零
这里就已经能够实现数字转中文了,继续写带有单位的计算转换,接着敲代码吧^_^。
上面例子的基础上,再作每四位一组,遍历单位转换,再加一个判断零的状况,代码以下:
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的享元模式。
客观看完点个赞,推荐推荐呗,嘿嘿~~
注:此系飞狐原创,转载请注明出处