聽飛狐聊JavaScript設計模式系列15

本回內容介紹

上一回,聊了聊鏈式編程,模擬了jQuery和underscore.js,並寫了一個遍歷多維數組的函數。
介一回,聊策略模式(Strategy),策略模式定義了一系列的算法,並將每個算法封裝起來,並且使它們還能夠相互替換。策略模式讓算法獨立於使用它的客戶而獨立變化。javascript

1. 策略模式

先看一個計算獎金的例子,這個在網上的策略模式例子裏曝光率很高,以下: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

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界面部分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轉一百二十三~

JS數字轉中文

中國古表明示數量的單位依次是:個、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));//零零一二六九零零二一二一零零二二三四二三二三四二四二一二零零一零

這裏就已經能夠實現數字轉中文了,繼續寫帶有單位的計算轉換,接着敲代碼吧^_^。

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的享元模式。

客觀看完點個贊,推薦推薦唄,嘿嘿~~


注:此係飛狐原創,轉載請註明出處

相關文章
相關標籤/搜索