JavaScript設計模式與開發實踐 策略模式

定義

  定義一系列算法,把它們封裝起來,並使它們能夠相互替換。具體來講就是,定義一系列算法,把它們各自封裝成策略類,算法被封裝在策略類內部的方法。在客戶對Context發起請求的時,Context老是把請求委託給策略對象中的某個方法計算。css

 

Javascript的策略模式

     // 把策略類定義爲函數        
     var strategies = {
        "S": function( salary ){
            return salary * 4;
        },
        "A": function( salary ){
            return salary * 3;
        },
        "B": function( salary ){
            return salary * 2;
        }
    };

    //用函數充當Context
    var calculateBonus = function( level, salary ){
        return strategies[ level ]( salary );
    };

    console.log( calculateBonus( 'S', 20000 ) ); // 輸出:80000
    console.log( calculateBonus( 'A', 10000 ) ); // 輸出:30000

 

 

使用策略模式實現緩動動畫

  編寫一些動畫類和緩動算法,讓小球以各類緩動效果運動。算法

  運動前至少要記錄的信息:小球原始位置、小球的目標位置、動畫開始的準確時間和小球運動的持續時間。app

  用setInterval建立定時器,每一幀把動畫已消耗事件 、小球原始位置、小球目標位置和動畫持續事件傳入緩動算法。該算法能算出小球當前應所處位置,並更新該div的CSS屬性。dom

 

  這些緩動算法來自Flash,分別接受4個參數:動畫已消耗的時間、小球原始位置、小球目標位置、動畫持續的總時間,返回的值是動畫元素應該所處的當前位置。函數

        var tween = {
            linear: function( t, b, c, d ){
                return c*t/d + b;
            },
            easeIn: function( t, b, c, d ){
                return c * ( t /= d ) * t + b;
            },
            strongEaseIn: function(t, b, c, d){
                return c * ( t /= d ) * t * t * t * t + b;
            },
            strongEaseOut: function(t, b, c, d){
                return c * ( ( t = t / d - 1) * t * t * t * t + 1 ) + b;
            },
            sineaseIn: function( t, b, c, d ){
                return c * ( t /= d) * t * t + b;
            },
            sineaseOut: function(t,b,c,d){
                return c * ( ( t = t / d - 1) * t * t + 1 ) + b;
            }
        };

 

    var Animate = function( dom ){
        this.dom = dom; // 進行運動的dom 節點
        this.startTime = 0; // 動畫開始時間
        this.startPos = 0; // 動畫開始時,dom 節點的位置,即dom 的初始位置
        this.endPos = 0; // 動畫結束時,dom 節點的位置,即dom 的目標位置
        this.propertyName = null; // dom 節點須要被改變的css 屬性名
        this.easing = null; // 緩動算法
        this.duration = null; // 動畫持續時間
    };

 
    //啓動動畫,記錄信息,並啓動定時器 
    Animate.prototype.start = function( propertyName, endPos, duration, easing ){
        this.startTime = +new Date; // 動畫啓動時間
        this.startPos = this.dom.getBoundingClientRect()[ propertyName ]; // dom 節點初始位置
        this.propertyName = propertyName; // dom 節點須要被改變的CSS 屬性名
        this.endPos = endPos; // dom 節點目標位置
        this.duration = duration; // 動畫持續事件
        this.easing = tween[ easing ]; // 緩動算法
        var self = this;
        var timeId = setInterval(function(){ // 啓動定時器,開始執行動畫
            if ( self.step() === false ){ // 若是動畫已結束,則清除定時器
                clearInterval( timeId );
            }
        }, 19 );
    };

    //表示小球每一幀要作的事:計算小球當前位置和調用CSS屬性值的方法
    Animate.prototype.step = function(){
        var t = +new Date; // 取得當前時間
        if ( t >= this.startTime + this.duration ){ // (1)
            this.update( this.endPos ); // 更新小球的CSS 屬性值
            return false;
        }
        var pos = this.easing( t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration );
        // pos 爲小球當前位置
            this.update( pos ); // 更新小球的CSS 屬性值
    };

    //更新小球CSS屬性值
    Animate.prototype.update = function( pos ){
        this.dom.style[ this.propertyName ] = pos + 'px';
    };

    var div = document.getElementById( 'div' );
    var animate = new Animate( div );
    animate.start( 'left', 500, 1000, 'strongEaseOut' );

 

表單驗證

用策略模式重構表單驗證

  把校驗邏輯封裝成策略對象:動畫

        var strategies = {
            isNonEmpty: 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;
                }
            }
        };

  實現Validator類做爲Conext,接收用戶的請求並委託給strategy對象:this

        var Validator = function(){
            this.cache = []; // 保存校驗規則
        };

        Validator.prototype.add = function( dom, rule, errorMsg ){
            var ary = rule.split( ':' );    // 把strategy 和length參數分開
            this.cache.push(function(){     // 構建ary參數,把匿名函數放進cache
                var strategy = ary.shift(); // 用戶挑選的strategy
                ary.unshift( dom.value );   // 把input 的value 添加進參數length前
                ary.push( errorMsg );       // 把errorMsg 添加進參數length後
                return strategies[ strategy ].apply( dom, ary );
            });
        };

        Validator.prototype.start = function(){
            for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
                var msg = validatorFunc(); // 開始校驗,並取得校驗後的返回信息
                if ( msg ){                // 若是有確切的返回值,說明校驗沒有經過
                    return msg;
                }
            }
        };

 

  建立validator對象,經過validator.add方向validator對象添加校驗規則。而後調用validator.start()方法啓動驗證。。若是validator.start()返回了確切的errorMsg字符串做爲返回值,說明驗證失敗。spa

        var validataFunc = function(){
            var validator = new Validator(); // 建立一個validator 對象
            /***************添加一些校驗規則****************/
            validator.add( registerForm.userName, 'isNonEmpty', '用戶名不能爲空' );
            validator.add( registerForm.password, 'minLength:6', '密碼長度不能少於6 位' );
            validator.add( registerForm.phoneNumber, 'isMobile', '手機號碼格式不正確' );
            var errorMsg = validator.start(); // 得到校驗結果
            return errorMsg; // 返回校驗結果
        }

        var registerForm = document.getElementById( 'registerForm' );
        registerForm.onsubmit = function(){
            var errorMsg = validataFunc(); // 若是errorMsg 有確切的返回值,說明未經過校驗
            if ( errorMsg ){
                alert ( errorMsg );
                return false; // 阻止表單提交
            }
        };

 

給某個文本輸入框添加多種驗證 

        /***********************策略對象**************************/
        var strategies = {
            isNonEmpty: 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;
                }
            }
        };
        /***********************Validator 類**************************/
        var Validator = function(){
            this.cache = [];
        };
        Validator.prototype.add = function( dom, rules ){
            var self = this;
            for ( var i = 0, rule; rule = rules[ i++ ]; ){
                (function( rule ){ var strategyAry = rule.strategy.split( ':' );
                    var errorMsg = rule.errorMsg;
                    self.cache.push(function(){
                        var strategy = strategyAry.shift();
                        strategyAry.unshift( dom.value );
                        strategyAry.push( errorMsg );
                        return strategies[ strategy ].apply( dom, strategyAry );
                    });
                })( rule )
            }
        };
        Validator.prototype.start = function(){
            for ( var i = 0, validatorFunc; validatorFunc =this.cache[i++]; ){
                var errorMsg = validatorFunc();
                if ( errorMsg ){
                    return errorMsg;
                }
            }
        };
        /***********************客戶調用代碼**************************/
        var registerForm = document.getElementById( 'registerForm' );
        var validataFunc = function(){
            var validator = new Validator();
            validator.add( registerForm.userName, [{
                strategy: 'isNonEmpty',
                errorMsg: '用戶名不能爲空'
            }, {
                strategy: 'minLength:6',
                errorMsg: '用戶名長度不能小於10 位'
            }]);
            validator.add( registerForm.password, [{
                strategy: 'minLength:8',
                errorMsg: '密碼長度不能小於6 位'
            }]);
            validator.add( registerForm.phoneNumber,[{
                strategy: 'isMobile',
                errorMsg: '手機號碼格式不正確'
            }] );
            var errorMsg = validator.start();
            return errorMsg;
        }
        registerForm.onsubmit = function(){
            var errorMsg = validataFunc();
            if ( errorMsg ){
                alert ( errorMsg );
                return false;
            }
        };

 

策略模式的優缺點prototype

優勢:code

  1. 利用組合、委託、多態等技術,有效避免多重條件選擇語句。
  2. 提供開放-封閉原則的完美支持。
  3. 策略模式的算法可用在其餘地方,避免許多複製、粘貼工做。
  4. 策略模式利用組合和委託讓Context擁有執行計算方法的能力,也是繼承的一種更輕便的替代方案。

缺點

  1. 在程序增長許多策略類或策略對象。
  2. 必須瞭解全部的strategy,必須瞭解各個strategy的不一樣點
相關文章
相關標籤/搜索