用生命谱写代码的赞歌

0%

JavaScript设计模式实践之策略模式

策略模式优缺点

  • 优点:

    1. 利用组合、委托和多态等技术和思想,有效的避免多重条件选择语句,优化分支判断模式
    2. 提供了开放-封闭原则,使代码更容易理解和扩展
    3. 可以更好地复用代码
  • 缺点

    1. 选择哪种算法决定权在用户,所以用户必须要了解每种算法的实现
    2. 由于策略模式的每种算法内部相互独立,所以对于一些复杂的算法处理相同逻辑的部分无法实现共享

计算奖金

年终奖根据员工的工资基数和年底绩效情况发放,绩效为 S 的人年终奖有5倍工资,绩效为 A 的人年终奖有4倍工资,而绩效为 B 的人年终奖是3倍工资,那么用代码简单实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
var calculateBonus = function(performanceLevel, salary) {
if (performanceLevel === 'S') {
return salary * 5;
}
if (performanceLevel === 'A') {
return salary * 4;
}
if (performanceLevel === 'B') {
return salary * 3;
}
};
calculateBonus('B', 20000); // 60000
calculateBonus('S', 6000); // 30000

上述代码实现缺点很明显:

  1. calculateBonus 比较庞大,内部包含很多if-else语句,这些语句需要覆盖所有的逻辑分支
  2. calculateBonus 缺乏弹性,如果新增绩效等级或者修改绩效等级的奖金系数,必须要深入到 calculateBonus 内部,违反了开放封闭原则
  3. 算法的复用性差,如果其他地方也用到类似的算法,但是计算规则不一样,代码无法复用

策略模式修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var bonusStrategy = function () {
var strategy = {
"S": function (salary) {
return salary * 5;
},
"A": function (salary) {
return salary * 4;
},
"B": function (salary) {
return salary * 3;

}
}
return function(algorithm, price) {
return strategy[algorithm] && strategy[algorithm](price)
}
}()

console.log(bonusStrategy('B', 20000)) // 60000
console.log(bonusStrategy('S', 6000)) // 30000

实现一个js当中运动的DIV

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
74
75
76
77
78
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>JS策略模式的应用</title>
</head>

<body>
<div style="position:absolute;background:blue; width: 100px;height: 100px;color: #fff;" id="div">我是div</div>
<script>
var tween = {
linear: function (t, b, c, d) {
return c * t / d + b;
},
easeIn: function (t, b, c, d) {
return c * (t /= d) * t + b;
},
strongEaseIn: function (t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
},
strongEaseOut: function (t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
},
sineaseIn: function (t, b, c, d) {
return c * (t /= d) * t * t + b;
},
sineaseOut: function (t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
}
};
var Animate = function (dom) {
this.dom = dom; // 进行运动的dom 节点
this.startTime = 0; // 动画开始时间
this.startPos = 0; // 动画开始时,dom 节点的位置,即dom 的初始位置
this.endPos = 0; // 动画结束时,dom 节点的位置,即dom 的目标位置
this.propertyName = null; // dom 节点需要被改变的css 属性名
this.easing = null; // 缓动算法
this.duration = null; // 动画持续时间
};
Animate.prototype.start = function (propertyName, endPos, duration, easing) {
this.startTime = +new Date; // 动画启动时间
this.startPos = this.dom.getBoundingClientRect()[propertyName]; // dom 节点初始位置
this.propertyName = propertyName; // dom 节点需要被改变的CSS 属性名
this.endPos = endPos; // dom 节点目标位置
this.duration = duration; // 动画持续事件
this.easing = tween[easing]; // 缓动算法
var self = this;
var timeId = setInterval(function () { // 启动定时器,开始执行动画
if (self.stop() === false) { // 如果动画已结束,则清除定时器
clearInterval(timeId);
}
}, 19);
};
Animate.prototype.stop = function () {
var t = +new Date; // 取得当前时间
if (t >= this.startTime + this.duration) { // (1)
this.update(this.endPos); // 更新小球的CSS 属性值
return false;
}
var pos = this.easing(t - this.startTime, this.startPos,
this.endPos - this.startPos, this.duration);
// pos 为小球当前位置
this.update(pos); // 更新小球的CSS 属性值
};
Animate.prototype.update = function (pos) {
this.dom.style[this.propertyName] = pos + 'px';
};

var div = document.getElementById('div');
var animate = new Animate(div);
animate.start('left', 400, 1000, 'sineaseOut');
</script>
</body>

</html>

实现表单验证

普通代码实现如下:

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>

<body>
<form action="http:// xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName"/><br>
请输入密码:<input type="text" name="password"/><br>

请输入手机号码:<input type="text" name="phoneNumber"/><br>
<button>提交</button><br>
</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
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>JS策略模式实现表单验证</title>
</head>

<body>
<form action="http://xxx.com/register" id="registerForm" method="post">
请输入用户名:<input type="text" name="userName" /><br>
请输入密码:<input type="text" name="password" /><br>

请输入手机号码:<input type="text" name="phoneNumber" /><br>
<button>提交</button><br>
</form>
<script>
// 策略对象
var strategies = {
isNotEmpty: 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, 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();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(dom, strategyAry);
});
})(rule)
}
};
Validator.prototype.start = function () {
for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
var errorMsg = validatorFunc();
if (errorMsg) {
return errorMsg;
}
}
};

// 客户调用代码
var registerForm = document.getElementById('registerForm');
var validataFunc = function () {
var validator = new Validator();
validator.add(registerForm.userName, [{
strategy: 'isNotEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于 6 位'
}]);
validator.add(registerForm.password, [{
strategy: 'minLength:8',
errorMsg: '密码长度不能小于 8 位'
}]);
validator.add(registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function () {
var errorMsg = validataFunc();
if (errorMsg) {
alert(errorMsg);
return false;
}
};
</script>
</body>

</html>