JavaScript 设计模式(中)——2.策略模式
2 策略模式
策略模式定义: 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换;
2.1 策略模式
策略模式的目的就是将算法的使用与算法的实现分离开来,将不变的部分和变化的部分隔开是每个设计模式的主题;
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。 第二个部分是环境类 Context, Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context 中要维持对某个策略对象的引用;
实例:积分模式(基础分乘上对于的等级倍数)
// 1. 最简单代码实现
var calculateBonus = function( level, base ){
if ( level === 'S' ){ return base * 4; }
if ( level === 'A' ){ return base * 3; }
};
calculateBonus( 'B', 10 ); // 输出: 40
calculateBonus( 'S', 5 ); // 输出: 15
// 1. 模仿传统面向对象语言中的策略模式实现
var performanceS = function(){};
performanceS.prototype.calculate = function( base ){
return base * 4;
};
var performanceA = function(){};
performanceA.prototype.calculate = function( base ){
return base * 3;
};
// 定义总分类 Bonus:
var Bonus = function(){
this.base = null; // 原始值
this.strategy = null; // 等级对应的策略对象
};
Bonus.prototype.setSalary = function( base ){
this.base = base; // 设置基础值
};
Bonus.prototype.setStrategy = function( strategy ){
this.strategy = strategy; // 设置等级对应的策略对象
};
Bonus.prototype.getBonus = function(){ // 取得总分数
return this.strategy.calculate( this.base ); // 把计算分数的操作委托给对应的策略对象
};
2.2 JavaScript 版本的策略模式
在 JavaScript 语言中,函数也是对象,所以更简单和直接的做法是把 strategy
直接定义为函数:
var strategies = {
"S": function( base ){ return base * 4; },
"A": function( base ){ return base * 3; },
};
var calculateBonus = function( level, base ){
return strategies[ level ]( base );
};
console.log( calculateBonus( 'S', 10 ) ); // 输出: 40
console.log( calculateBonus( 'A', 5 ) ); // 输出: 15
2.3 策略模式实现表单校验
// 步骤1. 封装策略对象
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;
}
}
};
// 步骤2. 实现 Validator 类(作为 Context,负责接收用户的请求并委托给 strategy 对象)
var Validator = function(){
this.cache = []; // 保存校验规则
};
Validator.prototype.add = function( dom, rule, errorMsg ){
var ary = rule.split( ':' ); // 把 strategy 和参数分开
this.cache.push(function(){ // 把校验的步骤用空函数包装起来,并且放入 cache
var strategy = ary.shift(); // 用户挑选的 strategy
ary.unshift( dom.value ); // 把 input 的 value 添加进参数列表
ary.push( errorMsg ); // 把 errorMsg 添加进参数列表
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;
}
}
};
// 步骤3. 向 Validator 类发送校验的请求
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; // 阻止表单提交
}
};
2.4 策略模式的优缺点
策略模式的优点:
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句;
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展;
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作;
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案;
策略模式的缺点:
- 使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在 Context 中要好;
- 使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点,这样才能选择一个合适的 strategy ;由于 strategy 要向客户暴露它的所有实现,这是违反最少知识原则;
2.5 一等函数对象与策略模式
之前的策略模式示例中,既有模拟传统面向对象语言的版本,也有针对 JavaScript
语言的特有实现。在以类为中心的传统面向对象语言中,不同的算法或者行为被封装在各个策略类中, Context
将请求委托给这些策略对象,这些策略对象会根据请求返回不同的执行结果,这样便能表现出对象的多态性;
在 JavaScript 中,除了使用类来封装算法和行为之外,直接使用函数也是一种选择。这些“算法”可以被封装到函数中并且四处传递,也就是常说的“高阶函数”。实际上在 JavaScript
这种将函数作为一等对象的语言里,策略模式已经融入到了语言本身当中,我们经常用高阶函数来封装不同的行为,并且把它传递到另一个函数中。当我们对这些函数发出“调用”的消息时,不同的函数会返回不同的执行结果;
var S = function( salary ){ return salary * 4; };
var A = function( salary ){ return salary * 3; };
var B = function( salary ){ return salary * 2; };
var calculateBonus = function( func, salary ){
return func( salary );
};
calculateBonus( S, 10 ); // 输出: 40
2.6 策略模式小结
本小节既有接近传统面向对象语言的策略模式实现,也有更适合 JavaScript 语言的策略模式版本。在 JavaScript 语言的策略模式中,策略类往往被函数所代替,这时策略模式就成为一种“隐形”的模式。
系列链接
- JavaScript 设计模式(上)——基础知识
- JavaScript 设计模式(中)——1.单例模式
- JavaScript 设计模式(中)——2.策略模式
- JavaScript 设计模式(中)——3.代理模式
- JavaScript 设计模式(中)——4.迭代器模式
- JavaScript 设计模式(中)——5.发布订阅模式
- JavaScript 设计模式(中)——6.命令模式
- JavaScript 设计模式(中)——7.组合模式
- JavaScript 设计模式(中)——8.模板方法模式
- JavaScript 设计模式(中)——9.享元模式
- JavaScript 设计模式(中)——10.职责链模式
- JavaScript 设计模式(中)——11. 中介者模式
- JavaScript 设计模式(中)——12. 装饰者模式
- JavaScript 设计模式(中)——13.状态模式
- JavaScript 设计模式(中)——14.适配器模式
- JavaScript 设计模式(下)——设计原则
- JavaScript 设计模式练习代码
本文主要参考了《JavaScript设计模式和开发实践》一书