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

Posted Amber丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《JavaScript设计模式与开发实践》—— 策略模式相关的知识,希望对你有一定的参考价值。

策略模式的定义是: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

 

(1) 使用策略模式计算奖金
经过思考,我们想到了更好的办法——使用策略模式来重构代码。策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类 ContextContext 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context 中要维持对某个策略对象的引用。
javascript 语言中,函数也是对象,所以更简单和直接的做法是把 strategy直接定义为函数:

// 定义策略模式算法
        var performance = {

            "S" : function ( salary ) {
                return salary * 4;
            },
            "A" : function ( salary ) {
                return salary * 3;
            },
            "B" : function ( salary ) {
                return salary * 2;
            },
            "C" : function ( salary ) {
                return salary;
            }

        };
    // 计算奖金方法
        var calculateBonus = function ( level , salary ) {
            return performance[level](salary);
        };
    // 测试
        console.log(calculateBonus("B",4200));  // 8400
        console.log(calculateBonus("S",6500));  // 26000

通过使用策略模式重构代码,我们消除了原程序中大片的条件分支语句。所有跟计算奖金有关的逻辑不再放在 Context 中,而是分布在各个策略对象中。 Context 并没有计算奖金的能力,而是把这个职责委托给了某个策略对象。每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出“计算奖金”的请求时,它们会返回各自不同的计算结果,这正是对象多态性的体现,也是“它们可以相互替换”的目的。替换 Context 中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果。


(2) 使用策略模式实现缓动动画


1. 实现动画效果的原理
JavaScript 实现动画效果的原理跟动画片的制作一样,动画片是把一些差距不大的原画以较快的帧数播放,来达到视觉上的动画效果。在 JavaScript 中,可以通过连续改变元素的某个 CSS属性,比如 lefttopbackground-position 来实现动画效果。
下图就是通过改变节点的background-position,让人物动起来的。

2. 思路和一些准备工作
我们目标是编写一个动画类和一些缓动算法,让小球以各种各样的缓动效果在页面中运动。
现在来分析实现这个程序的思路。在运动开始之前,需要提前记录一些有用的信息,至少包括以下信息:
动画开始时,小球所在的原始位置
小球移动的目标位置
动画开始时的准确时间点
小球运动持续的时间
随后,我们会用 setInterval 创建一个定时器,定时器每隔 19ms 循环一次。在定时器的每一帧里,我们会把动画已消耗的时间、小球原始位置、小球目标位置和动画持续的总时间等信息传入缓动算法。该算法会通过这几个参数,计算出小球当前应该所在的位置。最后再更新该 div 对应的 CSS 属性,小球就能够顺利地运动起来了。
3. 让小球动起来

 <div style="position:absolute; background: pink; border-radius: 50%; width: 60px; height: 60px;" id="div"></div>

    <script>
        /*========================================
        * t : Time —— 运动已消耗的时间
        * b : Begin value —— 开始值
        * c : Change In Value —— 结束值-开始值
        * d : Duration —— 总运动时间
        * 返回值是动画元素应该处在的当前位置
        ======================================== */
    // 运动算法
        var tween = {
            liner : 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位置
            this.endPos = 0;  // 动画结束的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 timer = setInterval( function () {  // 启动定时器,开始执行动画
                if ( self.step() === false ) {  // 如果动画已结束,则清除定时器
                    clearInterval( timer );
                }
            } , 19 );
        };
    //小球每一帧要做的事情
        Animate.prototype.step = function () {
            var t = +new Date;
            if ( t >= this.startTime + this.duration ) {
                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 , 6000 , \'sineaseIn\' );

    </script>

 

(3) 表单校验

<form action="" id="registerForm">
        请输入用户名:<input type="text" name="username">
        请输入密码:<input type="text" name="password">
        请输入手机号码:<input type="text" name="phoneNumber">
        <button>提交</button>
    </form>

    <script>
        var registerForm = document.getElementById( \'registerForm\' );

        registerForm.onsubmit = function () {
            if ( registerForm.username.value === \'\' ) {
                alert(\'用户名不能为空!\');
                return false;
            }
            if ( registerForm.password.length < 6 ) {
                alert(\'密码长度不能少于6位!\');
                return false;
            }
            if ( !/^1[3|5|8][0-9]{9}$/.test( registerForm.phoneNumber.value ) ) {
                alert(\'手机号码格式不正确\');
                return false;
            }
        }
    </script>

这是一种常见的编码方式,缺点也是显而易见的。

我们现在采用策略模式来实现它:

<form action="" id="registerForm">
        请输入用户名:<input type="text" name="username">
        请输入密码:<input type="text" name="password">
        请输入手机号码:<input type="text" name="phoneNumber">
        <button>提交</button>
    </form>

    <script>

        var strategies = {
            // 用户名不能为空
            isNonEmpty : function ( value , errMsg ) {
                if ( value === \'\' ) {
                    return errMsg;
                }
            },
            // 密码长度不能少于6位
            isMinLen : function ( value , length ,errMsg ) {
                if ( value.length < length ) {
                    return errMsg;
                }
            },
            // 手机格式不正确
            isMoblie : function ( value , errMsg ) {
                if ( !/^1[3|5|8][0-9]{9}$/.test( value ) ) {
                    return errMsg;
                }
            }

        };

        var validateFunc = function () {
            var validator = new Validator();
            // 添加校验规则
            validator.add( registerForm.userName , \'isNonEmpty\' , \'用户名不能为空\' );
            validator.add( registerForm.userName , \'isMinLen\' , \'密码长度不能少于6位\' );
            validator.add( registerForm.userName , \'isMoblie\' , \'手机格式不正确\' );
            var errMsg = validator.start();
            return errMsg;
        };

        var registerForm = document.getElementById( \'registerForm\' );
        registerForm.onsubmit = function () {
            var errMsg = validateFunc();  // 如果 errorMsg 有确切的返回值,说明未通过校验
            if ( errMsg ) {
                alert( errMsg );
                return false;  // 阻止表单提交
            }
        };

        var Validator = function () {
            this.cache = [];
        };
        Validator.prototype.add = function ( dom , rule , errMsg ) {
            var arr = rule.split( \':\' );
            this.cache.push(function () {
                var strategy = arr.shift();
                arr.unshift( dom.value );
                arr.push( errMsg );
                return strategies[ strategy ].apply( dom , arr );
            });
        };
        
        Validator.prototype.start = function () {
            for ( var i = 0,validatorFunc; validatorFunc = this.cache[ i++ ]; ) {
                var msg = validatorFunc();
                if ( msg ) {
                    return msg;
                }
            }
        }

    </script>

个人表示这个代码写的有点晦涩了,有很多地方看不太懂,理解不了。

以上是关于《JavaScript设计模式与开发实践》—— 策略模式的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript设计模式与开发实践:分时函数

Javascript设计模式与开发实践——面向对象的JavaScript(多态封装继承)

摘JavaScript设计模式与开发实践--单例模式

读书JavaScript 设计模式与开发实践

JavaScript设计模式与开发实践

《 javascript 设计模式与开发实践 》 ---发布-订阅模式 代码小问题