行为型-策略模式

概念

Strategy Pattern:又叫 政策模式,定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化。

使用策略模式时,我们可以定义一些策略类,每一个策略类中封装一种具体的算法。在这里,每一个封装算法的类我们都可以称之为一种策略,根据传入不同的策略类,使环境类执行不同策略类中的算法

img

使用

这里举例我们的表单验证,平时验证的时候,我会使用条件语句,如下:

1
2
3
4
5
6
7
8
9
10
11
12
<form action="http://xxx.com/register" id="registerForm" method="post">
<div class="form-group">
<label for="user">请输入用户名:</label>
<input type="text" class="form-control" id="user" name="userName">
</div>
<div class="form-group">
<label for="pwd">请输入密码:</label>
<input type="password" class="form-control" id="pwd" name="passWord">
</div>

<button type="button" class="btn btn-default">Submit</button>
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let registerForm = document.querySelector('#registerForm')
registerForm.addEventListener('submit', function() {
if (registerForm.userName.value === '') {
alert('用户名不能为空!')
return false
}
if (registerForm.userName.length < 6) {
alert('用户名长度不能少于6位!')
return false
}
if (registerForm.passWord.value === '') {
alert('密码不能为空!')
return false
}
}, false)

这样会有很多缺点:

  1. if-else 语句会很多,这些语句需要覆盖所有的校验规则
  2. 违反 “开闭原则”,如果需要增加一种新的校验规则或者更改校验规则,需要在函数内部进行修改
  3. 复用性很差,其他表格也需要类似的校验时,可能需要各种复制粘贴

使用策略模式后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/* Validator类 */
class Validator {
constructor() {
this.cache = [] //保存校验规则
}
add(dom, rules) {
for (let rule of rules) {
let strategyAry = rule.strategy.split(':') // 例如['minLength',6]
let errorMsg = rule.errorMsg // '用户名不能为空'
this.cache.push(() => {
let strategy = strategyAry.shift() // 用户挑选的strategy
strategyAry.unshift(dom.value) // 把input的value添加进参数列表
strategyAry.push(errorMsg) // 把errorMsg添加进参数列表,[dom.value,6,errorMsg]
return strategies[strategy].apply(dom, strategyAry)
})
}
}
start() {
for (let validatorFunc of this.cache) {
let errorMsg = validatorFunc()// 开始校验,并取得校验后的返回信息
if (errorMsg) {// 如果有确切返回值,说明校验没有通过
return errorMsg
}
}
}
}

/* 客户端调用代码 */
let registerForm = document.querySelector('#registerForm')

const validatorFunc = () => {
let validator = new Validator()

validator.add(registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空!'
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于6位!'
}])

validator.add(registerForm.passWord, [{
strategy: 'isNonEmpty',
errorMsg: '密码不能为空!'
}])
let errorMsg = validator.start()
return errorMsg
}

registerForm.addEventListener('submit', function() {
let errorMsg = validatorFunc()
if (errorMsg) {
alert(errorMsg)
return false
}
}, false)

优点

  • 符合 ”开闭原则“:可以灵活地增加新的算法或行为

  • 提供了管理相关的算法族的办法:策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码

  • 提供了一种可以替换继承关系的办法:如果不使用策略模式,那么使用算法的环境类就可能会有一些子类,每一个子类提供一种不同的算法。但是,这样一来算法的使用就和算法本身混在一起,不符合“单一职责原则”,决定使用哪一种算法的逻辑和该算法本身混合在一起,从而不可能再独立演化;而且使用继承无法实现算法或行为在程序运行时的动态切换

  • 可以避免多重条件选择语句:多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后

  • 提供了一种算法的复用机制:由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。换句话说,策略模式只适用于客户端知道所有的算法或行为的情况
  • 将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类
  • 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况