策略模式
策略模式:定义一些算法,把它们一个个封装起来,使它们可以互相替换。此模式让算法的变化不会影响到使用算法的客户。
应用场景:在设计程序中,当我们实现某种功能的时候,他有很多种选择,这些算法灵活多样,这时候我们需要用到策略模式,将每个算法封装起来,可以交替使用。实践中,不仅可以封装算法,也可以用来封装几乎任何类型的规则,是要在分析过程中需要在不同时间应用不同的业务规则,就可以考虑是要策略模式来处理各种变化。
策略模式的程序至少包含两个部分:第一部分是一组策略类,策略类封装了具体的算法,负责具体的计算过程;第二部分是环境类,环境类是接受用户的请求,随后把请求委托给某个策略类。
实现代码:
实例场景:在web项目中,我们常常会遇到注册、登录、修改等功能的实现,这都离不开表单的提交,在提交表单时,我们需要验证数据的合理性。如果用if-else语句来判断,这些语句需要覆盖所有的逻辑分支,同时,如果要增加新的条件,必须到这段代码内部进行修改,违反了开放-封闭原则,并且代码的复用性很差,这时,我们需要使用策略模式。
下面以表单提交为例,来理解策略模式的思想。
在不用策略模式的情况下,我们可能会这样处理:
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<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.value.length < 6 ){
alert ( '密码长度不能少于6位' );
return false;
}
if ( !/(^1[3|5|8][0-9]{9}$)/.test( registerForm.phoneNumber.value ) ){
alert ( '手机号码格式不正确' );
return false;
}
}
</script>
</body>
</html>
上述代码虽然实现了最基本的功能,但是存在的问题如下:
(1)registerForm.onsubmit函数中包含大量的if-else语句,必须包含所有的校验规则,这是函数的代码量比较庞大;
(2)registerForm.onsubmit函数缺乏弹性,如果要增加新的规则或修改某些规则,必须对该函数进行修改,不利于代码的维护,违反了开放-封闭规则;
(3)代码的可复用性较差,当其他地方也需要使用该校验规则时,必须复制该代码,导致代码混乱。
为了避免以上问题,我们用策略模式进行设计,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName"/>
请输入密码:<input type="text" name="password"/>
请输入手机号码:<input type="text" name="phoneNumber"/>
<button>提交</button>
</form>
<script>
//step1:将校验逻辑封装成策略对象,包含所有需要校验的信息
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;
}
}
};
// step2: Validator类作为环境类,接受用户的请求,用户的校验信息,随后把请求委托给strategies类
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 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;
}
}
};
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function () {
var errorMsg = validataFunc(); // 如果errorMsg有确切的返回值,说明未通过校验
if (errorMsg) {
alert(errorMsg);
return false; // 阻止表单提交
}
};
</script>
</body>
</html>
使用策略模式后,我们不需要使用if-else语句,仅仅通过“配置”的方式就可以完成表单的提交;当规则要添加或者修改时,只需要修改或编写少量代码即可完成,扩展性好;并且,代码有很好的复用性。
策略模式的优缺点:
优点:
1、策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
2、策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
3、策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
4、在策略模式中利用组合和委托来让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
缺点:
1、使用策略模式会在程序中增加许多策略类或者策略对象,但实际上这比把它们负责的逻辑堆砌在Context中要好。
2、要使用策略模式,必须了解所有的strategy,必须了解各个strategy之间的不
同点,这样才能选择一个合适的strategy。比如,我们要选择一种合适的旅游出行路
线,必须先了解选择飞机、火车、自行车等方案的细节。此时strategy要向客户暴露
它的所有实现,这是违反最少知识原则的。