#前言
本文聊的是策略模式,参考自《JavaScript 设计模式与开发实践》,相当于个人读书笔记。
#定义
策略模式的定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
一个基于策略模式的程序至少由两部分组成。一部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context 中要维持对某个策略对象的引用
#计算奖金
// 这个是策略类
const strategies = {
S(salary) {
return salary * 4;
},
A(salary) {
return salary * 3;
},
B(salary) {
return salary * 2;
}
};
// 这个是环境类 接收客户的请求
const calculateBonus = (level, salary) => {
return strategies[level](salary);
};
console.log(calculateBonus("S", 20000)); // 80000
console.log(calculateBonus("A", 10000)); // 30000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#实现缓动动画
<!-- 这是html代码 -->
<div
id="box"
style="position: absolute; width: 100px; height: 100px; background-color: blueviolet;"
>
test box
</div>
1
2
3
4
5
6
7
2
3
4
5
6
7
// 策略模式定义动画效果,这是策略类
const tween = {
linear(t, b, c, d) {
return (c * t) / d + b;
},
easeIn(t, b, c, d) {
return c * (t /= d) * t + b;
},
strongEaseIn(t, b, c, d) {
return c * (t /= d) * t ** 4 + b;
},
strongEaseOut(t, b, c, d) {
return c * ((t = t / d - 1) * t ** 4 + 1) + b;
},
sineaseIn(t, b, c, d) {
return c * (t /= d) * t * t + b;
},
sineaseOut(t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
}
};
class Animate {
constructor(dom) {
this.dom = dom;
this.startTime = 0;
this.startPos = 0;
this.endPos = 0;
this.propertyName = null; // dom节点需要被改变的css属性名,例如left, right
this.easing = null; // 动画效果
this.duration = null; // 动画持续时间
}
start(propertyName, endPos, duration, easing) {
this.startTime = Date.now();
this.startPos = this.dom.getBoundingClientRect()[propertyName];
this.propertyName = propertyName;
this.endPos = endPos;
this.duration = duration;
this.easing = tween[easing]; // 这是环境类
const timeId = setInterval(() => {
if (this.step() === false) {
clearInterval(timeId);
}
}, 19);
}
step() {
let t = Date.now();
if (t >= this.startTime + this.duration) {
this.updated(this.endPos);
return false;
}
let pos = this.easing(
t - this.startTime,
this.startPos,
this.endPos - this.startPos,
this.duration
);
this.updated(pos);
}
updated(pos) {
this.dom.style[this.propertyName] = pos + "px";
}
}
// 测试动画
const box = document.getElementById("box");
const animate = new Animate(box);
animate.start("left", 500, 1000, "strongEaseOut");
// animate.start("top", 500, 1000, "strongEaseOut");
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#实现表单校验
<!-- html代码 -->
<form action="/test.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="username" /> 请输入密码:<input
type="text"
name="password"
/>
请输入手机号码: <input type="text" name="phoneNumber" />
<button type="submit">提交</button>
</form>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
#第一版
没有使用策略模式:
const registerForm = document.getElementById("registerForm");
registerForm.addEventListener("submit", e => {
if (registerForm.username.value === "") {
alert("用户名不能为空");
return e.preventDefault();
}
if (registerForm.password.value.length < 6) {
alert("密码长度不能少于6位");
return e.preventDefault();
}
if (!/^1[3|5|8][0-9]{9}$/.test(registerForm.phoneNumber.value)) {
alert("手机号码格式不正确");
return e.preventDefault();
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#第二版
使用策略模式增加可拓展性
// 策略类
const strategies = {
isNonEmpty(value, errorMsg) {
if (value === "") {
return errorMsg;
}
},
minLength(value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
isMobile(value, errorMsg) {
if (!/^1[3|5|8][0-9]{9}$/.test(value)) {
return errorMsg;
}
}
};
// 环境类
class Validator {
constructor() {
this.cache = [];
}
add(dom, rule, errorMsg) {
const ary = rule.split(":");
this.cache.push(() => {
const strategy = ary.shift(); // 取出规则名
ary.unshift(dom.value); // 第一个参数为value值
ary.push(errorMsg); // 第二个参数为errorMsg值
return strategies[strategy].apply(dom, ary);
});
}
start() {
for (let i = 0; i < this.cache.length; i++) {
let msg = this.cache[i]();
if (msg) {
return msg;
}
}
}
}
const validateFunc = form => {
const validate = new Validator();
validate.add(form.username, "isNonEmpty", "用户名不能为空");
validate.add(form.password, "minLength:6", "密码长度不能少于6位");
validate.add(form.phoneNumber, "isMobile", "手机号码格式不正确");
const errorMsg = validate.start();
return errorMsg;
};
const registerForm = document.getElementById("registerForm");
registerForm.addEventListener("submit", e => {
const errorMsg = validateFunc(registerForm);
if (errorMsg) {
alert(errorMsg);
return e.preventDefault();
}
});
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
57
58
59
60
61
62
63
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
57
58
59
60
61
62
63
#单个表单多个校验
validate.add
方法中第二个参数变成数组实现单个表单的多个校验
class Validator {
constructor() {
this.cache = [];
}
add(dom, rule) {
for (let i = 0; i < rule.length; i++) {
const errorMsg = rule[i].errorMsg;
const ary = rule[i].strategy.split(":");
this.cache.push(() => {
const strategy = ary.shift(); // 取出规则名
ary.unshift(dom.value); // 第一个参数为value值
ary.push(errorMsg); // 第二个参数为errorMsg值
return strategies[strategy].apply(dom, ary);
});
}
}
start() {
for (let i = 0; i < this.cache.length; i++) {
let msg = this.cache[i]();
if (msg) {
return msg;
}
}
}
}
// 策略类
const strategies = {
isNonEmpty(value, errorMsg) {
if (value === "") {
return errorMsg;
}
},
minLength(value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
},
isMobile(value, errorMsg) {
if (!/^1[3|5|8][0-9]{9}$/.test(value)) {
return errorMsg;
}
}
};
const validateFunc = form => {
const validate = new Validator();
validate.add(form.username, [
{ strategy: "isNonEmpty", errorMsg: "用户名不能为空" },
{ strategy: "minLength:6", errorMsg: "用户名长度不能少于6位" }
]);
validate.add(form.password, [
{ strategy: "minLength:6", errorMsg: "密码长度不能少于6位" }
]);
validate.add(form.phoneNumber, [
{ strategy: "isMobile", errorMsg: "手机号码格式不正确" }
]);
const errorMsg = validate.start();
return errorMsg;
};
const registerForm = document.getElementById("registerForm");
registerForm.addEventListener("submit", e => {
e.preventDefault();
const errorMsg = validateFunc(registerForm);
if (errorMsg) {
alert(errorMsg);
return e.preventDefault();
}
});
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#总结
优点:
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句
- 策略模式提供了对开放-封闭原则的完美支持,将算法封装在独立的 strategy,使得它们易于切换,易于理解,易于扩展
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作
- 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,是继承中轻便的替代方案
缺点:
- 使程序增加许多策略类或者策略对象
- strategy 要向客户暴露它所有实现,同时了解各个 strategy 不同之处