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; } };
策略模式的优缺点
优点:
- 利用组合、委托、多态等技术,有效避免多重条件选择语句。
- 提供开放-封闭原则的完美支持。
- 策略模式的算法可用在其他地方,避免许多复制、粘贴工作。
- 策略模式利用组合和委托让Context拥有执行计算方法的能力,也是继承的一种更轻便的替代方案。
缺点
- 在程序增加许多策略类或策略对象。
- 必须了解所有的strategy,必须了解各个strategy的不同点