定义
策略模式指的是定义一系 列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策 略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体 的算法,并负责具体的计算过程。 第二个部分是环境类 Context,Context接受客户的请求,随后 把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用
<!--algorithm-->
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)
}
更广义的“算法
在实际开发中,我们通常会把算法的含义扩散开来,使策略模式也可以用来封装 一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以 用策略模式来封装它们。
实战
背景
在一个 Web项目中,注册、登录、修改用户信息等功能的实现都离不开提交表单。 在将用户输入的数据交给后台之前,常常要做一些客户端力所能及的校验工作,比如注册的 时候需要校验是否填写了用户名,密码的长度是否符合规定,等等。这样可以避免因为提交不合 法数据而带来的不必要网络开销。
假设我们正在编写一个注册的页面,在点击注册按钮之前,有如下几条校验逻辑。
- 用户名不能为空。
- 密码长度不能少于 6位。
- 手机号码必须符合格式。
表单校验的第一个版本(传统版本)
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName"/ >
请输入密码:<input type="text" name="password"/ >
</body>
var registerForm = document.getElementById( 'registerForm' );
registerForm.onsubmit = function(){
if ( registerForm.userName.value === '' ){
alert ( '用户名不能为空' );
return false;
}
if ( registerForm.password.value.length < 6 ){
alert ( '密码长度不能少于 6 位' );
return false;
}
if ( !/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value ) ){
alert ( '手机号码格式不正确' );
return false;
}
}
- registerForm.onsubmit 函数比较庞大,包含了很多 if-else 语句,这些语句需要覆盖所有的校验规则。
- registerForm.onsubmit 函数缺乏弹性,如果增加了一种新的校验规则,或者想把密码的长
- 度校验从 6改成 8,我们都必须深入 registerForm.onsubmit 函数的内部实现,这是违反开放 — 封闭原则的。
- 算法的复用性差,如果在程序中增加了另外一个表单,这个表单也需要进行一些类似的校验,那我们很可能将这些校验逻辑复制得漫天遍野。
用策略模式重构表单校验
下面我们将用策略模式来重构表单校验的代码,很显然第一步我们要把这些校验逻辑都封装 成策略对象:
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, rule, errorMsg){
var ary = rule.split(':');// 把 strategy 和参数分开
this.cache.push(function(){// 把校验的步骤用空函数包装起来,并且放入 cache
var strategy = ary.shifg();// 用户挑选的 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;
}
}
}
使用策略模式重构代码之后,我们仅仅通过“配置”的方式就可以完成一个表单的校验, 这些校验规则也可以复用在程序的任何地方,还能作为插件的形式,方便地被移植到其他项 目中。
在修改某个校验规则的时候,只需要编写或者改写少量的代码。比如我们想将用户名输入框 的校验规则改成用户名不能少于 4个字符。可以看到,这时候的修改是毫不费力的。代码如下:
validator.add( registerForm.userName, 'isNonEmpty', '用户名不能为空' );
// 改成:
validator.add( registerForm.userName, 'minLength:10', '用户名长度不能小于 10 位' );
给某个文本输入框添加多种校验规则
<!--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();
})
})(rule)
}
}
Validator.prototype.start = function(){
for(var i=0, validatorFunc;validatorFunc = this.cache[i++];){
var errorMsg = validatorFunc();
if(errorMsg){
return errorMsg;
}
}
}
策略模式的优缺点
优点:
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放 — 封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让 Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
缺点:
- 使用策略模式会在程序中增加许多策略类或者策略对象
- ,要使用策略模式,必须了解所有的 strategy ,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy 。