策略模式的定义是: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
(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>
个人表示这个代码写的有点晦涩了,有很多地方看不太懂,理解不了。