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

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

(1) 使用策略模式计算奖金
经过思考,我们想到了更好的办法——使用策略模式来重构代码。策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来。

一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类 Context, Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 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属性,比如 left、 top、 background-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>

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

时间: 2024-10-25 21:18:54

《JavaScript设计模式与开发实践》—— 策略模式的相关文章

《Android源码设计模式解析》读书笔记——Android中你应该知道的设计模式

断断续续的,<Android源码设计模式解析>也看了一遍,书中提到了很多的设计模式,但是有部分在开发中见到的几率很小,所以掌握不了也没有太大影响. 我觉得这本书的最大价值有两点,一个是从设计模式的角度去理解Android源码,结合着日常开发中的常用类,对设计模式的理解会更加的深刻:另外一个好处就是了解常用模式,再看其他人写的代码的时候,更容易理解代码思路.下面是我的读书笔记和一些思考,设计模式只整理我认为重要的部分. 建造者模式 建造者模式最明显的标志就是Build类,而在Android中最常

《Android源码设计模式解析与实战》读书笔记(十四)

第十四章.迭代器模式 迭代器模式,又叫做游标模式,是行为型设计模式之一.我们知道对容器对象的访问必然会涉及遍历算法,我们可以将遍历的方法封装在容器中,或者不提供遍历方法,让使用容器的人自己去实现去吧.这两种情况好像都能够解决问题. 然而在前一种情况,容器承受了过多的功能,它不仅要负责自己"容器"内的元素维护(添加.删除等等),而且还要提供遍历自身的接口:而且由于遍历状态保存的问题,不能对同一个容器对象同时进行多个遍历.第二种方式倒是省事,却又将容器的内部细节暴露无遗. 正因于此,迭代器

《Android源码设计模式解析与实战》读书笔记(十三)

第十三章.备忘录模式 备忘录模式是一种行为模式,该模式用于保存对象当前的状态,并且在之后可以再次恢复到此状态,有点像是我们平常说的"后悔药". 1.定义 在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象恢复到原先保存的状态. 2.使用场景 (1)需要保存一个对象在某一个时刻的状态或部分状态. (2)如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访

《Android源码设计模式解析与实战》读书笔记(十)

第十章.解释器模式 解释器模式是一种用的比较少的行为型模式,其提供了一种解释语言的语法或表达式的方式.但是它的使用场景确实很广泛,只是因为我们自己很少回去构造一个语言的文法,所以使用较少. 1.定义 给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子.(其中语言就是我们需要解释的对象,文法就是这个语言的规律,解释器就是翻译机,通过文法来翻译语言.) 2.使用场景 1.如果某个简单的语言需要解释执行而且可以将该语言中的语句表示为一个抽象的语法树时可以考虑使

《Android源码设计模式解析与实战》读书笔记(十八)

第十八章.代理模式 代理模式也称委托模式,是结构型设计模式之一.是应用广泛的模式之一. 1.定义 为其他对象提供一种代理以控制对这个对象的访问. 2.使用场景 当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口. 3.UML类图 (1)Subject:抽象主题类,声明真实主题与共同接口方法,该类可以是抽象类或接口. (2)RealSubject:真实主题类(被委托类),尤其执行具体的业务逻辑方法.

《Android源码设计模式解析与实战》读书笔记(二十二)

第二十二章.享元模式 享元模式是结构型设计模式之一,是对对象池的一种实现.就像它的名字一样,共享对象,避免重复的创建.我们常用的String 就是使用了共享模式,所以String类型的对象创建后就不可改变,如果当两个String对象所包含的内容相同时,JVM只创建一个String对象对应这两个不同的对象引用. 1.定义 采用一个共享来避免大量拥有相同内容对象的开销.使用享元模式可有效支持大量的细粒度对象. 2.使用场景 (1)系统中存在大量的相似对象. (2)细粒度的对象都具备较接近的外部状态,

《Android源码设计模式解析与实战》读书笔记(十一)

第十一章.命令模式 命令模式是行为型模式之一.总体来说并不难理解,只是比较繁琐,他会将简单的调用关系解耦成多个部分,增加类的复杂度,但是即便如此,命令模式的结构依然清晰. 1.定义 将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化:对请求排队或者记录请求日志,以及支持可撤销的操作. 2.使用场景 (1)需要抽出待执行的动作,然后以参数的形式提供出来. (2)在不同的时刻指定.排列和执行请求.一个命令对象可以有与初始请求无关的生存期. (3)需要支持操作取消. (4)支持修改日志功

《Android源码设计模式解析与实战》读书笔记(二十一)

第二十一章.装饰模式 装饰模式也称为包装模式,是结构型设计模式之一.装饰模式是一种用于替代继承技术的一种方案. 1.定义 动态的给一个对象添加一些额外的职责.就增加功能来说,装饰模式相比生成子类更为灵活. 2.使用场景 (1)需要透明且动态地扩展类的功能时.且在不影响其他对象的情况下. (2)当不能采用继承对系统进行扩展时可以使用装饰模式.比如final类. 3.UML类图 (1)Component:抽象组件.可以是一个接口或抽象类,其充当的就是被装饰的原始对象. (2)ConcreteComp

《Android源码设计模式解析与实战》读书笔记(十二)

第十二章.观察者模式 观察者模式是一个使用率非常高的模式,它最常用在GUI系统.订阅–发布系统.因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们之间的依赖性更小,甚至做到毫无依赖.比如安卓的开源项目EventBus.Otto.AndroidEventBus等事件总线类的和RxJava响应式编程其核心都是使用观察者模式. 1.定义 观察者模式是一种行为类模式,它定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新. 2.使用场景

《Android源码设计模式解析与实战》读书笔记(二十四)

第二十四章.桥接模式 桥接模式也称为桥梁模式,是结构型设计模式之一.桥接模式中体现了"单一职责原则"."开闭原则"."里氏替换原则"."依赖倒置原则"等.同时它也是很实用的一种模式. 1.定义 将抽象部分与现实部分分离,使它们都可以独立地进行变化. 2.使用场景 (1)如果一个系统需要在构建的抽象化角色和具体角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系. (2)对于那些不希望使用继承或因为多层次继承导致系统类