JavaScript设计模式与开发实践 策略模式

定义

  定义一系列算法,把它们封装起来,并使它们可以相互替换。具体来说就是,定义一系列算法,把它们各自封装成策略类,算法被封装在策略类内部的方法。在客户对Context发起请求的时,Context总是把请求委托给策略对象中的某个方法计算。

 

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

 

 

使用策略模式实现缓动动画

  编写一些动画类和缓动算法,让小球以各种缓动效果运动。

  运动前至少要记录的信息:小球原始位置、小球的目标位置、动画开始的准确时间和小球运动的持续时间。

  用setInterval创建定时器,每一帧把动画已消耗事件 、小球原始位置、小球目标位置和动画持续事件传入缓动算法。该算法能算出小球当前应所处位置,并更新该div的CSS属性。

 

  这些缓动算法来自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对象:

        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字符串作为返回值,说明验证失败。

        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;
            }
        };

 

策略模式的优缺点

优点:

  1. 利用组合、委托、多态等技术,有效避免多重条件选择语句。
  2. 提供开放-封闭原则的完美支持。
  3. 策略模式的算法可用在其他地方,避免许多复制、粘贴工作。
  4. 策略模式利用组合和委托让Context拥有执行计算方法的能力,也是继承的一种更轻便的替代方案。

缺点

  1. 在程序增加许多策略类或策略对象。
  2. 必须了解所有的strategy,必须了解各个strategy的不同点

 

posted on 2016-11-11 10:29  Surahe  阅读(162)  评论(0编辑  收藏  举报